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];
- {displayReviews.map((review, index) => ( + {displayReviews.map((review, index) => { + const fullName = `${review.name} ${review.surname || ''}`.trim(); + const colorIndex = index % 6; + return (
- ))} + ); + })}
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