Новые правки в компоенты

This commit is contained in:
Web-serfer 2026-04-19 19:00:42 +05:00
parent 36a3d37ad3
commit 815986969a
19 changed files with 1703 additions and 143 deletions

View file

@ -0,0 +1,132 @@
import type { APIRoute } from "astro";
const POCKETBASE_URL = import.meta.env.POCKETBASE_URL || "http://localhost:8090";
export const POST: APIRoute = async ({ request, cookies }) => {
const pbAuthCookie = cookies.get("pb_auth")?.value;
const token = pbAuthCookie?.trim();
if (!token) {
return new Response(
JSON.stringify({ error: "Unauthorized" }),
{ status: 401, headers: { "Content-Type": "application/json" } }
);
}
try {
let userId: string | null = null;
const userResponse = await fetch(
`${POCKETBASE_URL}/api/collections/users/auth-refresh`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
}
);
if (userResponse.ok) {
const userData = await userResponse.json();
userId = userData.record?.id;
}
if (!userId) {
return new Response(
JSON.stringify({ error: "Unauthorized" }),
{ status: 401, headers: { "Content-Type": "application/json" } }
);
}
const body = await request.json();
const { name, surname, profession, rating, text } = body;
if (!name || !surname || !profession || !rating || !text) {
return new Response(
JSON.stringify({ error: "Все поля обязательны" }),
{ status: 400, headers: { "Content-Type": "application/json" } }
);
}
if (text.length < 50 || text.length > 2000) {
return new Response(
JSON.stringify({ error: "Текст отзыва должен быть от 50 до 2000 символов" }),
{ status: 400, headers: { "Content-Type": "application/json" } }
);
}
if (rating < 1 || rating > 5) {
return new Response(
JSON.stringify({ error: "Оценка должна быть от 1 до 5" }),
{ status: 400, headers: { "Content-Type": "application/json" } }
);
}
const response = await fetch(
`${POCKETBASE_URL}/api/collections/reviews/records`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
name,
surname,
profession,
rating,
text,
status: "pending",
votesCount: 0,
user: userId,
}),
}
);
const data = await response.json();
if (!response.ok) {
return new Response(JSON.stringify(data), {
status: response.status,
headers: { "Content-Type": "application/json" },
});
}
return new Response(JSON.stringify(data), {
status: 201,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.error("[Reviews API POST] Error:", error);
return new Response(
JSON.stringify({ error: "Failed to create review" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
}
};
export const GET: APIRoute = async ({ url }) => {
const status = url.searchParams.get("status") || "published";
const expand = "user";
const sort = "-created";
let filter = `status = "${status}"`;
try {
const response = await fetch(
`${POCKETBASE_URL}/api/collections/reviews/records?expand=${expand}&filter=${encodeURIComponent(filter)}&sort=${sort}`,
{}
);
const data = await response.json();
return new Response(JSON.stringify(data), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.error("[Reviews API GET] Error:", error);
return new Response(
JSON.stringify({ error: "Failed to fetch reviews" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
}
};

View file

@ -1,6 +1,6 @@
---
import Layout from '@layouts/Layout.astro';
import PageHero from '@components/base/PageHero.astro';
import Hero from '@components/home/Hero.astro';
import Services from "@components/home/Services.astro";
import Steps from "@components/home/Steps.astro";
import WhyUs from "@components/home/WhyUs.astro";
@ -14,7 +14,7 @@ import { SITE_URL } from '@constants';
description="Профессиональная юридическая помощь автовладельцам в Сургуте. Споры со страховыми, возврат прав, ДТП, споры с автосалонами."
canonicalLink={SITE_URL}
>
<PageHero
<Hero
badgeText="ЗАЩИТА ВОДИТЕЛЕЙ В СУРГУТЕ"
titleWhite="Защитите свои права"
titleGold="и водительское удостоверение"

View file

@ -4,13 +4,12 @@ import { SITE_URL } from '@constants';
import PageHero from '@components/base/PageHero.astro';
import Pagination from '@components/base/Pagination.astro';
import ReviewCard from '@components/reviews/ReviewCard.astro';
import ReviewFormContainer from '@components/reviews/ReviewFormContainer.tsx';
import ReviewsList from '@components/reviews/ReviewsList.tsx';
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);
@ -48,39 +47,8 @@ const paginatedReviews = reviewsData.slice(startIndex, endIndex);
<section class="reviews-page">
<div class="site-container">
<!-- Блок статистики голосования -->
<VotingSummary
averageRating={votingSummary.averageRating}
totalVotes={votingSummary.totalVotes}
totalReviews={votingSummary.totalReviews}
ratingDistribution={votingSummary.ratingDistribution}
/>
<!-- Сетка отзывов -->
<div class="reviews-grid">
{paginatedReviews.map((review) => (
<ReviewCard
name={review.name}
car={review.car}
text={review.text}
rating={review.rating}
initial={review.initial}
color={review.color}
date={review.date}
votesCount={review.votesCount}
isHelpful={review.isHelpful}
/>
))}
</div>
<!-- Пагинация -->
{totalPages > 1 && (
<Pagination
currentPage={currentPage}
totalPages={totalPages}
baseUrl="/reviews"
/>
)}
<!-- Блок статистики голосования -->
<ReviewsList client:load />
<!-- Форма для отзыва -->
<section class="review-form-section">
@ -94,80 +62,7 @@ const paginatedReviews = reviewsData.slice(startIndex, endIndex);
</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"
/>
)}
<ReviewFormContainer client:load />
</div>
</section>
</div>
@ -311,20 +206,6 @@ 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();