Создан компонент поста
This commit is contained in:
parent
674ef7fe04
commit
d0f41672d1
32 changed files with 2082 additions and 289 deletions
168
frontend/src/pages/blog/[slug].astro
Normal file
168
frontend/src/pages/blog/[slug].astro
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
---
|
||||
import ArticleLayout from '@layouts/ArticleLayout.astro';
|
||||
import { SITE_URL } from '@constants';
|
||||
import PostCommentForm from '@components/blog/PostCommentForm.astro';
|
||||
import RelatedPosts from '@components/blog/RelatedPosts.astro';
|
||||
import { getCollection, getEntry, render } from 'astro:content';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection('blog') as { id: string; data: Record<string, any> }[];
|
||||
return posts.map((post: { id: string }) => ({
|
||||
params: { slug: post.id },
|
||||
}));
|
||||
}
|
||||
|
||||
const slug = Astro.params.slug;
|
||||
|
||||
if (!slug) {
|
||||
return Astro.redirect('/blog');
|
||||
}
|
||||
|
||||
const post = await getEntry('blog', slug);
|
||||
|
||||
if (!post) {
|
||||
return Astro.redirect('/blog');
|
||||
}
|
||||
|
||||
const { Content } = await render(post);
|
||||
|
||||
// Форматируем дату
|
||||
const formatDate = (date: Date) => {
|
||||
return date.toLocaleDateString('ru-RU', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
// Логика авторизации (пока статичная переменная)
|
||||
const isAuthorized = false;
|
||||
|
||||
// URL текущей страницы
|
||||
const currentUrl = `${SITE_URL}/blog/${post.id}`;
|
||||
|
||||
// Получаем все посты для блока "Читайте также"
|
||||
const allPosts = await getCollection('blog');
|
||||
---
|
||||
|
||||
<ArticleLayout
|
||||
title={`${post.data.title} — автоюрист в Сургуте`}
|
||||
description={post.data.description}
|
||||
canonicalLink={`${SITE_URL}/blog/${post.id}`}
|
||||
breadcrumbs={[
|
||||
{ label: 'Главная', href: '/' },
|
||||
{ label: 'Блог', href: '/blog' },
|
||||
{ label: post.data.title }
|
||||
]}
|
||||
heroImage={post.data.imageUrl}
|
||||
heroAlt={post.data.title}
|
||||
category={post.data.category}
|
||||
postTitle={post.data.title}
|
||||
date={formatDate(post.data.date)}
|
||||
author={post.data.author}
|
||||
readTime={post.data.readTime}
|
||||
postId={post.id}
|
||||
postUrl={currentUrl}
|
||||
initialLikes={12}
|
||||
initialDislikes={2}
|
||||
>
|
||||
<!-- Содержимое статьи -->
|
||||
<div class="post-content">
|
||||
<Content />
|
||||
</div>
|
||||
|
||||
<!-- Форма комментариев -->
|
||||
<PostCommentForm
|
||||
postId={post.id}
|
||||
isAuthorized={isAuthorized}
|
||||
/>
|
||||
|
||||
<!-- Похожие статьи -->
|
||||
<RelatedPosts
|
||||
posts={allPosts}
|
||||
currentSlug={post.id}
|
||||
/>
|
||||
</ArticleLayout>
|
||||
|
||||
<style>
|
||||
/* Post Content */
|
||||
.post-content {
|
||||
padding: 3rem;
|
||||
color: #334155;
|
||||
line-height: 1.8;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.post-content :global(h2) {
|
||||
color: #1e293b;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
margin: 2rem 0 1rem;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.post-content :global(h3) {
|
||||
color: #1e293b;
|
||||
font-size: 1.375rem;
|
||||
font-weight: 700;
|
||||
margin: 1.75rem 0 1rem;
|
||||
}
|
||||
|
||||
.post-content :global(p) {
|
||||
margin: 0 0 1.25rem;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.post-content :global(ul), .post-content :global(ol) {
|
||||
margin: 1rem 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.post-content :global(li) {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.post-content :global(blockquote) {
|
||||
margin: 2rem 0;
|
||||
padding: 1.5rem 2rem;
|
||||
background: #f8fafc;
|
||||
border-left: 4px solid #d4af37;
|
||||
border-radius: 0 0.75rem 0.75rem 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.post-content :global(blockquote p) {
|
||||
margin: 0;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.post-content :global(strong) {
|
||||
color: #1e293b;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.post-content :global(code) {
|
||||
background: #f1f5f9;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.post-content {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.post-content :global(h2) {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.post-content :global(h3) {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -4,19 +4,35 @@ import { SITE_URL } from '@constants';
|
|||
import PageHero from '@components/base/PageHero.astro';
|
||||
import BlogCategories from '@components/blog/BlogCategories.astro';
|
||||
import BlogCard from '@components/blog/BlogCard.astro';
|
||||
import BlogPagination from '@components/blog/BlogPagination.astro';
|
||||
import Pagination from '@components/base/Pagination.astro';
|
||||
import CTA from '@components/base/CTA.astro';
|
||||
import SearchModal from '@components/base/SearchModal.astro';
|
||||
import { blogPosts, categories } from '@data/blogData';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
const posts = await getCollection('blog');
|
||||
|
||||
// Сортируем посты по дате (новые сверху)
|
||||
const sortedPosts = posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
|
||||
|
||||
const POSTS_PER_PAGE = 6;
|
||||
const currentPage = 1;
|
||||
const totalPages = Math.ceil(blogPosts.length / POSTS_PER_PAGE);
|
||||
const totalPages = Math.ceil(sortedPosts.length / POSTS_PER_PAGE);
|
||||
|
||||
const startIndex = 0;
|
||||
const endIndex = POSTS_PER_PAGE;
|
||||
const paginatedPosts = blogPosts.slice(startIndex, endIndex);
|
||||
const paginatedPosts = sortedPosts.slice(startIndex, endIndex);
|
||||
|
||||
// Форматируем дату
|
||||
const formatDate = (date: Date) => {
|
||||
return date.toLocaleDateString('ru-RU', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
// Категории
|
||||
const categories = ['Все', ...new Set(posts.map((post: any) => post.data.category))];
|
||||
---
|
||||
|
||||
<Layout
|
||||
|
|
@ -49,17 +65,17 @@ const paginatedPosts = blogPosts.slice(startIndex, endIndex);
|
|||
<section class="blog-grid-section">
|
||||
<div class="site-container">
|
||||
<div class="blog-grid" id="blog-grid">
|
||||
{paginatedPosts.map((post) => (
|
||||
<article class="blog-card-wrapper" data-category={post.category}>
|
||||
{paginatedPosts.map((post: any) => (
|
||||
<article class="blog-card-wrapper" data-category={post.data.category}>
|
||||
<BlogCard
|
||||
title={post.title}
|
||||
description={post.description}
|
||||
category={post.category}
|
||||
categoryColor={post.categoryColor}
|
||||
date={post.date}
|
||||
readTime={post.readTime}
|
||||
imageUrl={post.imageUrl}
|
||||
slug={post.slug}
|
||||
title={post.data.title}
|
||||
description={post.data.description}
|
||||
category={post.data.category}
|
||||
categoryColor={post.data.categoryColor}
|
||||
date={formatDate(post.data.date)}
|
||||
readTime={post.data.readTime}
|
||||
imageUrl={post.data.imageUrl}
|
||||
slug={`/blog/${post.id}`}
|
||||
/>
|
||||
</article>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -4,19 +4,35 @@ import { SITE_URL } from '@constants';
|
|||
import PageHero from '@components/base/PageHero.astro';
|
||||
import BlogCategories from '@components/blog/BlogCategories.astro';
|
||||
import BlogCard from '@components/blog/BlogCard.astro';
|
||||
import BlogPagination from '@components/blog/BlogPagination.astro';
|
||||
import Pagination from '@components/base/Pagination.astro';
|
||||
import CTA from '@components/base/CTA.astro';
|
||||
import SearchModal from '@components/base/SearchModal.astro';
|
||||
import { blogPosts, categories } from '@data/blogData';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
const posts = await getCollection('blog');
|
||||
|
||||
// Сортируем посты по дате (новые сверху)
|
||||
const sortedPosts = posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
|
||||
|
||||
const POSTS_PER_PAGE = 6;
|
||||
const currentPage = Number(Astro.params.page) || 1;
|
||||
const totalPages = Math.ceil(blogPosts.length / POSTS_PER_PAGE);
|
||||
const totalPages = Math.ceil(sortedPosts.length / POSTS_PER_PAGE);
|
||||
|
||||
const startIndex = (currentPage - 1) * POSTS_PER_PAGE;
|
||||
const endIndex = startIndex + POSTS_PER_PAGE;
|
||||
const paginatedPosts = blogPosts.slice(startIndex, endIndex);
|
||||
const paginatedPosts = sortedPosts.slice(startIndex, endIndex);
|
||||
|
||||
// Форматируем дату
|
||||
const formatDate = (date: Date) => {
|
||||
return date.toLocaleDateString('ru-RU', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
// Категории
|
||||
const categories = ['Все', ...new Set(posts.map(post => post.data.category))];
|
||||
---
|
||||
|
||||
<Layout
|
||||
|
|
@ -50,17 +66,17 @@ const paginatedPosts = blogPosts.slice(startIndex, endIndex);
|
|||
<section class="blog-grid-section">
|
||||
<div class="site-container">
|
||||
<div class="blog-grid" id="blog-grid">
|
||||
{paginatedPosts.map((post) => (
|
||||
<article class="blog-card-wrapper" data-category={post.category}>
|
||||
{paginatedPosts.map((post: any) => (
|
||||
<article class="blog-card-wrapper" data-category={post.data.category}>
|
||||
<BlogCard
|
||||
title={post.title}
|
||||
description={post.description}
|
||||
category={post.category}
|
||||
categoryColor={post.categoryColor}
|
||||
date={post.date}
|
||||
readTime={post.readTime}
|
||||
imageUrl={post.imageUrl}
|
||||
slug={post.slug}
|
||||
title={post.data.title}
|
||||
description={post.data.description}
|
||||
category={post.data.category}
|
||||
categoryColor={post.data.categoryColor}
|
||||
date={formatDate(post.data.date)}
|
||||
readTime={post.data.readTime}
|
||||
imageUrl={post.data.imageUrl}
|
||||
slug={`/blog/${post.id}`}
|
||||
/>
|
||||
</article>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ import Layout from '@layouts/Layout.astro';
|
|||
import { SITE_URL } from '@constants';
|
||||
import BlogCard from '@components/blog/BlogCard.astro';
|
||||
import SearchModal from '@components/base/SearchModal.astro';
|
||||
import { blogPosts } from '@data/blogData';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
const posts = await getCollection('blog');
|
||||
|
||||
// Получаем параметр поиска из URL
|
||||
const url = new URL(Astro.request.url);
|
||||
|
|
@ -16,21 +18,30 @@ const breadcrumbsItems = [
|
|||
];
|
||||
|
||||
// Функция поиска по статьям
|
||||
function searchArticles(query: string) {
|
||||
function searchArticles(query: string, allPosts: typeof posts) {
|
||||
if (!query.trim()) return [];
|
||||
|
||||
|
||||
const lowerQuery = query.toLowerCase();
|
||||
|
||||
return blogPosts.filter(post => {
|
||||
const titleMatch = post.title.toLowerCase().includes(lowerQuery);
|
||||
const descriptionMatch = post.description.toLowerCase().includes(lowerQuery);
|
||||
const categoryMatch = post.category.toLowerCase().includes(lowerQuery);
|
||||
|
||||
|
||||
return allPosts.filter(post => {
|
||||
const titleMatch = post.data.title.toLowerCase().includes(lowerQuery);
|
||||
const descriptionMatch = post.data.description.toLowerCase().includes(lowerQuery);
|
||||
const categoryMatch = post.data.category.toLowerCase().includes(lowerQuery);
|
||||
|
||||
return titleMatch || descriptionMatch || categoryMatch;
|
||||
});
|
||||
}
|
||||
|
||||
const searchResults = searchArticles(searchQuery);
|
||||
const searchResults = searchArticles(searchQuery, posts);
|
||||
|
||||
// Форматируем дату
|
||||
const formatDate = (date: Date) => {
|
||||
return date.toLocaleDateString('ru-RU', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
---
|
||||
|
||||
<Layout
|
||||
|
|
@ -46,20 +57,20 @@ const searchResults = searchArticles(searchQuery);
|
|||
<h1 class="search-title">
|
||||
{searchQuery ? `Результаты поиска` : 'Поиск статей'}
|
||||
</h1>
|
||||
|
||||
|
||||
{searchQuery && (
|
||||
<div class="search-info">
|
||||
<p class="search-query-text">
|
||||
По запросу: <span class="query-highlight">"{searchQuery}"</span>
|
||||
</p>
|
||||
<p class="search-count">
|
||||
{searchResults.length === 0
|
||||
? 'Ничего не найдено'
|
||||
{searchResults.length === 0
|
||||
? 'Ничего не найдено'
|
||||
: `Найдено статей: ${searchResults.length}`}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<button class="open-search-btn" id="open-search-btn" data-modal-target="search-modal">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
|
|
@ -76,16 +87,16 @@ const searchResults = searchArticles(searchQuery);
|
|||
<section class="results-section">
|
||||
<div class="site-container">
|
||||
<div class="results-grid">
|
||||
{searchResults.map((post) => (
|
||||
{searchResults.map((post: any) => (
|
||||
<BlogCard
|
||||
title={post.title}
|
||||
description={post.description}
|
||||
category={post.category}
|
||||
categoryColor={post.categoryColor}
|
||||
date={post.date}
|
||||
readTime={post.readTime}
|
||||
imageUrl={post.imageUrl}
|
||||
slug={post.slug}
|
||||
title={post.data.title}
|
||||
description={post.data.description}
|
||||
category={post.data.category}
|
||||
categoryColor={post.data.categoryColor}
|
||||
date={formatDate(post.data.date)}
|
||||
readTime={post.data.readTime}
|
||||
imageUrl={post.data.imageUrl}
|
||||
slug={`/blog/${post.id}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -131,10 +142,10 @@ const searchResults = searchArticles(searchQuery);
|
|||
<script>
|
||||
(function() {
|
||||
const btn = document.getElementById('open-search-btn');
|
||||
|
||||
|
||||
btn?.addEventListener('click', () => {
|
||||
window.dispatchEvent(new CustomEvent('open-modal', {
|
||||
detail: 'search-modal'
|
||||
window.dispatchEvent(new CustomEvent('open-modal', {
|
||||
detail: 'search-modal'
|
||||
}));
|
||||
});
|
||||
|
||||
|
|
@ -142,11 +153,11 @@ const searchResults = searchArticles(searchQuery);
|
|||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const query = urlParams.get('q');
|
||||
|
||||
|
||||
if (!query && btn) {
|
||||
setTimeout(() => {
|
||||
window.dispatchEvent(new CustomEvent('open-modal', {
|
||||
detail: 'search-modal'
|
||||
window.dispatchEvent(new CustomEvent('open-modal', {
|
||||
detail: 'search-modal'
|
||||
}));
|
||||
}, 300);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import Layout from '@layouts/Layout.astro';
|
||||
import { SITE_URL, COMPANY } from '@constants';
|
||||
import PageHero from '@components/base/PageHero.astro';
|
||||
import CTA from '@components/base/CTA.astro';
|
||||
import AuthLockBlock from '@components/base/AuthLockBlock.astro';
|
||||
|
||||
// Логика авторизации (пока статичная переменная)
|
||||
const isAuthorized = false; // Измените на true, чтобы увидеть форму
|
||||
|
|
@ -23,13 +23,13 @@ const isAuthorized = false; // Измените на true, чтобы увиде
|
|||
titleGold="с нами"
|
||||
description="Мы всегда на связи и готовы помочь вам в решении автоспоров. Оставьте заявку или позвоните — первая консультация бесплатно."
|
||||
layout="with-image"
|
||||
sideImage="/images/home/avtourist-surgut.avif"
|
||||
sideImage="/images/contacts/conImg.avif"
|
||||
sideImageAlt="Автоюрист Сургут"
|
||||
experienceBadge={{
|
||||
number: "15",
|
||||
text: "МИНУТ НА СВЯЗИ"
|
||||
}}
|
||||
bgImage="/images/home/bg_hero.avif"
|
||||
bgImage="/images/contacts/conBg.avif"
|
||||
/>
|
||||
|
||||
<!-- Карточки контактов -->
|
||||
|
|
@ -159,28 +159,16 @@ const isAuthorized = false; // Измените на true, чтобы увиде
|
|||
</p>
|
||||
</form>
|
||||
) : (
|
||||
<div class="auth-lock-card animate-on-scroll" data-animation="fade-up" data-delay="200">
|
||||
<div class="lock-icon-container">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lock-icon">
|
||||
<rect width="18" height="11" x="3" y="11" rx="2" ry="2"></rect>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="lock-title">Форма доступна только клиентам</h3>
|
||||
<p class="lock-text">Чтобы отправить сообщение напрямую юристу, пожалуйста, авторизуйтесь в личном кабинете.</p>
|
||||
<a href="/auth/sign-in" class="auth-button">Войти в кабинет</a>
|
||||
</div>
|
||||
<AuthLockBlock
|
||||
title="Авторизуйтесь, чтобы написать"
|
||||
description="Чтобы отправить сообщение напрямую юристу, пожалуйста, войдите в личный кабинет."
|
||||
buttonText="Войти в кабинет"
|
||||
buttonHref="/auth/sign-in"
|
||||
className="animate-on-scroll"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA-блок -->
|
||||
<CTA
|
||||
icon="phone"
|
||||
title="Нужна срочная помощь?"
|
||||
description="Запишитесь на бесплатную консультацию прямо сейчас — мы перезвоним в течение 15 минут"
|
||||
btnText="Записаться на консультацию"
|
||||
/>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
|
|
@ -247,7 +235,7 @@ const isAuthorized = false; // Измените на true, чтобы увиде
|
|||
|
||||
.hours-icon { width: 1rem; height: 1rem; }
|
||||
|
||||
/* ===== FORM SECTION & AUTH LOCK ===== */
|
||||
/* ===== FORM SECTION ===== */
|
||||
.contact-form-section {
|
||||
padding: 5rem 0;
|
||||
background: #ffffff;
|
||||
|
|
@ -257,49 +245,6 @@ const isAuthorized = false; // Измените на true, чтобы увиде
|
|||
.section-title { font-size: clamp(2rem, 4vw, 2.5rem); font-weight: 800; color: #1e293b; margin: 0 0 1rem; }
|
||||
.section-description { color: #64748b; font-size: 1.1rem; margin: 0; }
|
||||
|
||||
/* Auth Lock Card */
|
||||
.auth-lock-card {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: #f8fafc;
|
||||
border: 2px dashed #e2e8f0;
|
||||
border-radius: 1.5rem;
|
||||
padding: 4rem 2rem;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.lock-icon-container {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
background: #ffffff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 1.5rem;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.lock-icon { width: 2rem; height: 2rem; }
|
||||
.lock-title { color: #1e293b; font-size: 1.5rem; font-weight: 700; margin: 0 0 1rem; }
|
||||
.lock-text { color: #64748b; line-height: 1.6; margin: 0 0 2rem; max-width: 400px; }
|
||||
|
||||
.auth-button {
|
||||
background: #1e293b;
|
||||
color: #ffffff;
|
||||
padding: 0.875rem 2rem;
|
||||
border-radius: 0.75rem;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.auth-button:hover { background: #0f172a; transform: translateY(-2px); }
|
||||
|
||||
/* Contact Form Styles */
|
||||
.contact-form {
|
||||
max-width: 700px;
|
||||
|
|
@ -359,7 +304,6 @@ const isAuthorized = false; // Измените на true, чтобы увиде
|
|||
@media (max-width: 768px) {
|
||||
.cards-grid { grid-template-columns: 1fr; }
|
||||
.form-row { grid-template-columns: 1fr; }
|
||||
.auth-lock-card { padding: 3rem 1.5rem; }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@
|
|||
import Layout from '@layouts/Layout.astro';
|
||||
import { SITE_URL } from '@constants';
|
||||
import PageHero from '@components/base/PageHero.astro';
|
||||
import CTA from '@components/base/CTA.astro';
|
||||
import Pagination from '@components/base/Pagination.astro';
|
||||
import ReviewCard from '@components/reviews/ReviewCard.astro';
|
||||
import VotingSummary from '@components/reviews/VotingSummary.astro';
|
||||
import AuthLockBlock from '@components/base/AuthLockBlock.astro';
|
||||
import { reviewsData, votingSummary } from '@data/reviewsData';
|
||||
|
||||
// Логика авторизации (пока статичная переменная)
|
||||
const isAuthorized = true; // Измените на true, чтобы увидеть форму
|
||||
|
||||
const REVIEWS_PER_PAGE = 6;
|
||||
const currentPage = 1;
|
||||
const totalPages = Math.ceil(reviewsData.length / REVIEWS_PER_PAGE);
|
||||
|
|
@ -32,13 +35,13 @@ const paginatedReviews = reviewsData.slice(startIndex, endIndex);
|
|||
titleGold="водителей из Сургута"
|
||||
description="Узнайте, как мы помогли нашим клиентам решить их проблемы с автоспорами"
|
||||
layout="with-image"
|
||||
sideImage="/images/home/avtourist-surgut.avif"
|
||||
sideImage="/images/reviews/revImg.avif"
|
||||
sideImageAlt="Автоюрист Сургут"
|
||||
experienceBadge={{
|
||||
number: "95%",
|
||||
text: "ДОВОЛЬНЫХ КЛИЕНТОВ"
|
||||
}}
|
||||
bgImage="/images/home/bg_hero.avif"
|
||||
bgImage="/images/reviews/revBg.avif"
|
||||
/>
|
||||
|
||||
<section class="reviews-page">
|
||||
|
|
@ -78,14 +81,94 @@ const paginatedReviews = reviewsData.slice(startIndex, endIndex);
|
|||
/>
|
||||
)}
|
||||
|
||||
<!-- CTA блок -->
|
||||
<CTA
|
||||
icon="chat"
|
||||
title="Хотите оставить отзыв?"
|
||||
description="Поделитесь своим опытом работы с нами. Ваш отзыв поможет другим водителям принять правильное решение."
|
||||
btnText="Оставить отзыв"
|
||||
btnHref="/contacts"
|
||||
/>
|
||||
<!-- Форма для отзыва -->
|
||||
<section class="review-form-section">
|
||||
<div class="site-container">
|
||||
<div class="form-header">
|
||||
<h2 class="section-title animate-on-scroll" data-animation="fade-up">
|
||||
Оставьте <span class="text-gold">отзыв</span>
|
||||
</h2>
|
||||
<p class="section-description animate-on-scroll" data-animation="fade-up" data-delay="100">
|
||||
Поделитесь своим опытом — ваше мнение важно для нас
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{isAuthorized ? (
|
||||
<form class="review-form animate-on-scroll" data-animation="fade-up" data-delay="200" action="#" method="POST" id="review-form">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="review-name" class="form-label">Имя *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="review-name"
|
||||
name="name"
|
||||
class="form-input"
|
||||
placeholder="Иван"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="review-surname" class="form-label">Фамилия *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="review-surname"
|
||||
name="surname"
|
||||
class="form-input"
|
||||
placeholder="Иванов"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="review-rating" class="form-label">Оценка *</label>
|
||||
<select
|
||||
id="review-rating"
|
||||
name="rating"
|
||||
class="form-input"
|
||||
required
|
||||
>
|
||||
<option value="">Выберите оценку</option>
|
||||
<option value="5">5 — Отлично</option>
|
||||
<option value="4">4 — Хорошо</option>
|
||||
<option value="3">3 — Удовлетворительно</option>
|
||||
<option value="2">2 — Плохо</option>
|
||||
<option value="1">1 — Очень плохо</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="review-text" class="form-label">Ваш отзыв *</label>
|
||||
<textarea
|
||||
id="review-text"
|
||||
name="review"
|
||||
class="form-textarea"
|
||||
placeholder="Расскажите о вашем опыте работы с нами..."
|
||||
rows="5"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="submit-btn">
|
||||
Отправить отзыв
|
||||
</button>
|
||||
|
||||
<p class="form-privacy">
|
||||
Нажимая кнопку, вы соглашаетесь с
|
||||
<a href="/privacy" class="privacy-link">политикой конфиденциальности</a>
|
||||
</p>
|
||||
</form>
|
||||
) : (
|
||||
<AuthLockBlock
|
||||
title="Авторизуйтесь, чтобы оставить отзыв"
|
||||
description="Чтобы поделиться своим опытом, пожалуйста, войдите в личный кабинет."
|
||||
buttonText="Войти в кабинет"
|
||||
buttonHref="/auth/sign-in"
|
||||
className="animate-on-scroll"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
|
|
@ -104,48 +187,64 @@ const paginatedReviews = reviewsData.slice(startIndex, endIndex);
|
|||
margin: 3rem 0;
|
||||
}
|
||||
|
||||
/* CTA блок */
|
||||
.cta-section {
|
||||
margin-top: 4rem;
|
||||
padding: 3rem;
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
border-radius: 1.5rem;
|
||||
text-align: center;
|
||||
/* ===== REVIEW FORM SECTION ===== */
|
||||
.review-form-section {
|
||||
padding: 5rem 0;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
color: #ffffff;
|
||||
font-size: clamp(1.5rem, 3vw, 2rem);
|
||||
font-weight: 800;
|
||||
margin: 0 0 1rem 0;
|
||||
letter-spacing: -0.02em;
|
||||
.form-header { text-align: center; margin-bottom: 3rem; }
|
||||
.section-title { font-size: clamp(2rem, 4vw, 2.5rem); font-weight: 800; color: #1e293b; margin: 0 0 1rem; }
|
||||
.section-description { color: #64748b; font-size: 1.1rem; margin: 0; }
|
||||
|
||||
/* Review Form Styles */
|
||||
.review-form {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.cta-text {
|
||||
color: #cbd5e1;
|
||||
font-size: 1.125rem;
|
||||
max-width: 600px;
|
||||
margin: 0 auto 2rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
|
||||
.form-group { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.form-label { font-size: 0.9rem; font-weight: 600; color: #1e293b; }
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 1rem 2.5rem;
|
||||
background: linear-gradient(135deg, #d4af37 0%, #fbbf24 100%);
|
||||
color: #1e293b;
|
||||
font-weight: 700;
|
||||
font-size: 1.125rem;
|
||||
text-decoration: none;
|
||||
.form-input, .form-textarea {
|
||||
padding: 0.875rem 1rem;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 0.75rem;
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
transition: all 0.2s ease;
|
||||
outline: none;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.form-input:focus, .form-textarea:focus {
|
||||
border-color: #d4af37;
|
||||
box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.1);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.form-textarea { resize: vertical; min-height: 140px; }
|
||||
|
||||
.submit-btn {
|
||||
background: linear-gradient(135deg, #d4af37 0%, #eac26e 100%);
|
||||
color: #1e293b;
|
||||
border: none;
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 0.75rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 4px 15px rgba(212, 175, 55, 0.3);
|
||||
}
|
||||
|
||||
.cta-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(212, 175, 55, 0.4);
|
||||
}
|
||||
.submit-btn:hover { transform: translateY(-3px); box-shadow: 0 8px 25px rgba(212, 175, 55, 0.4); }
|
||||
.form-privacy { font-size: 0.8rem; color: #94a3b8; text-align: center; }
|
||||
.privacy-link { color: #d4af37; text-decoration: underline; }
|
||||
|
||||
/* Анимации */
|
||||
.animate-on-scroll {
|
||||
|
|
@ -178,9 +277,7 @@ const paginatedReviews = reviewsData.slice(startIndex, endIndex);
|
|||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
padding: 2rem;
|
||||
}
|
||||
.form-row { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -213,11 +310,27 @@ const paginatedReviews = reviewsData.slice(startIndex, endIndex);
|
|||
});
|
||||
};
|
||||
|
||||
// Обработка формы отзыва
|
||||
const setupReviewForm = () => {
|
||||
const form = document.getElementById('review-form') as HTMLFormElement;
|
||||
if (form) {
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(form);
|
||||
console.log('Отправка отзыва:', Object.fromEntries(formData));
|
||||
alert('Спасибо! Ваш отзыв отправлен на модерацию.');
|
||||
form.reset();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Запуск
|
||||
setupAnimations();
|
||||
setupReviewForm();
|
||||
|
||||
// Для поддержки View Transitions в Astro
|
||||
document.addEventListener('astro:after-swap', () => {
|
||||
setupAnimations();
|
||||
setupReviewForm();
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue