diff --git a/frontend/src/components/home/Reviews.astro b/frontend/src/components/home/Reviews.astro
index b851425..1ce87f9 100644
--- a/frontend/src/components/home/Reviews.astro
+++ b/frontend/src/components/home/Reviews.astro
@@ -1,21 +1,42 @@
---
-import { reviewsData } from '@data/reviewsData';
import ReviewCard from '@components/reviews/ReviewCard.astro';
-// Берём 6 последних отзывов
-const latestReviews = [...reviewsData]
- .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
- .slice(0, 6);
+interface ReviewRecord {
+ id: string;
+ name: string;
+ surname?: string;
+ profession?: string;
+ text: string;
+ rating: number;
+ votesCount: number;
+ created: string;
+}
-// Для эффекта бесконечности клонируем элементы
-const displayReviews = [...latestReviews, ...latestReviews, ...latestReviews];
+const POCKETBASE_URL = import.meta.env.POCKETBASE_URL || 'http://127.0.0.1:8090';
+
+let reviews: ReviewRecord[] = [];
+
+try {
+ const filter = 'status = "published"';
+ const response = await fetch(
+ `${POCKETBASE_URL}/api/collections/reviews/records?filter=${encodeURIComponent(filter)}&sort=-created&perPage=10`
+ );
+
+ if (response.ok) {
+ const data = await response.json();
+ reviews = data.items || [];
+ }
+} catch (e) {
+ console.error('[ReviewsSlider] Error:', e);
+}
+
+const displayReviews = reviews.length >= 3
+ ? [...reviews, ...reviews, ...reviews]
+ : [...reviews, ...reviews];
-// Функция для получения инициала из имени
const getInitial = (name: string) => name.charAt(0).toUpperCase();
-// Функция для получения цвета аватара (циклически из набора)
-const colors = ['#eac26e', '#22c55e', '#3b82f6', '#ef4444', '#8b5cf6', '#f59e0b'];
-const getColor = (index: number) => colors[index % colors.length];
+const colors = ['bg-gradient-1', 'bg-gradient-2', 'bg-gradient-3', 'bg-gradient-4', 'bg-gradient-5', 'bg-gradient-6'];
---
@@ -49,25 +70,29 @@ const getColor = (index: number) => colors[index % colors.length];
diff --git a/frontend/src/components/reviews/ReviewCard.astro b/frontend/src/components/reviews/ReviewCard.astro
index 677312a..19c502d 100644
--- a/frontend/src/components/reviews/ReviewCard.astro
+++ b/frontend/src/components/reviews/ReviewCard.astro
@@ -46,7 +46,7 @@ const gradientColors = {
};
---
-
+
@@ -480,10 +480,11 @@ const gradientColors = {
document.querySelectorAll('.review-card').forEach((card) => {
const reviewId = (card as HTMLElement).dataset.reviewId;
+ const initialLikes = parseInt((card as HTMLElement).dataset.initialLikes || '0', 10);
const likeBtn = card.querySelector('[data-vote="likes"]') as HTMLButtonElement;
const likesCount = card.querySelector('[data-likes-count]');
- console.log('[ReviewCard] reviewId:', reviewId, 'likeBtn:', likeBtn, 'likesCount:', likesCount);
+ console.log('[ReviewCard] reviewId:', reviewId, 'likeBtn:', likeBtn, 'likesCount:', likesCount, 'initialLikes:', initialLikes);
if (!reviewId) {
console.error('[ReviewCard] No reviewId found!');
@@ -492,12 +493,6 @@ const gradientColors = {
async function loadVotes() {
console.log('[ReviewCard] Loading votes for:', reviewId);
- if (!pb.authStore.isValid) {
- console.log('[ReviewCard] Not logged in, keeping initialLikes:', initialLikes);
- if (likesCount) likesCount.textContent = initialLikes.toString();
- likeBtn?.classList.remove('active');
- return;
- }
try {
const response = await fetch(`/api/reviews/vote?review_id=${reviewId}`, {
credentials: 'include',
@@ -507,12 +502,17 @@ const gradientColors = {
const data = await response.json();
console.log('[ReviewCard] Votes data:', data);
if (likesCount) likesCount.textContent = data.likes.toString();
- if (data.userVote === 'likes') {
+ if (data.userVote === 'likes' && pb.authStore.isValid) {
likeBtn?.classList.add('active');
+ } else {
+ likeBtn?.classList.remove('active');
}
+ } else {
+ if (likesCount) likesCount.textContent = initialLikes.toString();
}
} catch (e) {
console.error('[ReviewCard] Error loading votes:', e);
+ if (likesCount) likesCount.textContent = initialLikes.toString();
}
}
diff --git a/frontend/src/components/reviews/ReviewsList.astro b/frontend/src/components/reviews/ReviewsList.astro
index dfdf333..91cc088 100644
--- a/frontend/src/components/reviews/ReviewsList.astro
+++ b/frontend/src/components/reviews/ReviewsList.astro
@@ -116,6 +116,7 @@ const getAvatarInfo = (name: string) => {
color={avatarInfo.color}
date={review.created}
reviewId={review.id}
+ initialLikes={review.votesCount || 0}
/>
);
})
diff --git a/frontend/src/pages/api/reviews/vote.ts b/frontend/src/pages/api/reviews/vote.ts
index 8f5e990..2a6925d 100644
--- a/frontend/src/pages/api/reviews/vote.ts
+++ b/frontend/src/pages/api/reviews/vote.ts
@@ -132,6 +132,15 @@ export const POST: APIRoute = async ({ request, cookies }) => {
console.error('[ReviewVote API] Votes error:', errorText);
}
+ await fetch(`${POCKETBASE_URL}/api/collections/reviews/records/${review_id}`, {
+ method: 'PATCH',
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ votesCount: likes }),
+ });
+
return new Response(
JSON.stringify({ likes, dislikes, userVote }),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
diff --git a/scripts/create-review.ts b/scripts/create-review.ts
new file mode 100644
index 0000000..6d4b1ed
--- /dev/null
+++ b/scripts/create-review.ts
@@ -0,0 +1,76 @@
+import PocketBase from 'pocketbase';
+
+const PB_URL = process.env.POCKETBASE_URL || 'http://127.0.0.1:8090';
+const ADMIN_EMAIL = process.env.PB_ADMIN_EMAIL;
+const ADMIN_PASSWORD = process.env.PB_ADMIN_PASSWORD;
+
+async function createReview() {
+ if (!ADMIN_EMAIL || !ADMIN_PASSWORD) {
+ console.error('❌ Укажите PB_ADMIN_EMAIL и PB_ADMIN_PASSWORD в .env');
+ process.exit(1);
+ }
+
+ const pb = new PocketBase(PB_URL);
+
+ try {
+ await pb.admins.authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
+ console.log('✅ Подключено к PocketBase');
+ } catch (e) {
+ console.error('❌ Ошибка авторизации admin');
+ process.exit(1);
+ }
+
+ const testEmail = 'vasiliy.sinicin@yandex.ru';
+ const testPassword = 'TestPass123!';
+
+ let userId: string;
+
+ try {
+ const existingUser = await pb.collection('users').getList(1, 1, {
+ filter: `email = "${testEmail}"`,
+ });
+
+ if (existingUser.items.length > 0) {
+ userId = existingUser.items[0].id;
+ console.log('ℹ️ Использован существующий пользователь:', userId);
+ } else {
+ const user = await pb.collection('users').create({
+ email: testEmail,
+ password: testPassword,
+ passwordConfirm: testPassword,
+ name: 'Тестовый Пользователь',
+ });
+ userId = user.id;
+ console.log('✅ Создан пользователь:', userId);
+ }
+ } catch (e: any) {
+ console.error('❌ Ошибка создания пользователя:', e.data || e.message);
+ process.exit(1);
+ }
+
+ try {
+ const review = await pb.collection('reviews').create({
+ user: userId,
+ name: 'Василий',
+ surname: 'Синицин',
+ profession: 'Предприниматель',
+ text: 'Помогли вернуть права после лишения. Всё сделали быстро и профессионально. Через 2 месяца права уже были у меня. Рекомендую!',
+ rating: 5,
+ votesCount: 0,
+ status: 'published',
+ });
+
+ console.log('✅ Отзыв создан');
+ console.log(' ID:', review.id);
+ console.log(' rating:', review.rating);
+ console.log(' status:', review.status);
+ } catch (e: any) {
+ if (e.data?.message?.includes('already exists')) {
+ console.log('ℹ️ Отзыв уже существует');
+ } else {
+ console.error('❌ Ошибка:', e.data || e.message);
+ }
+ }
+}
+
+createReview();
\ No newline at end of file