Новые правки в компоенты
This commit is contained in:
parent
36a3d37ad3
commit
815986969a
19 changed files with 1703 additions and 143 deletions
132
frontend/src/pages/api/reviews/index.ts
Normal file
132
frontend/src/pages/api/reviews/index.ts
Normal 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" } }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -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="и водительское удостоверение"
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue