Files
nahodka-front/src/pages/ReviewsPage.jsx
2026-09-03 00:29:19 +03:00

160 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect, useMemo, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { getImageUrl } from "../config/authConstants";
import { useAuthContext } from "../context/AuthContext";
import Sidebar from "../components/Sidebar";
import AppTopBar from "../components/layout/AppTopBar";
import { ProfileModal } from "../components/profile";
import AuthModal from "../components/AuthModal";
import RulesModal from "./RulesModal";
import ReviewCard from "../components/reviews/ReviewCard";
import ReviewForm from "../components/reviews/ReviewForm";
import RatingSummary from "../components/reviews/RatingSummary";
import { useReviews } from "../hooks/useReviews";
import "../styles/reviews.css";
import { useTranslation } from 'react-i18next';
import { useProfile } from "../hooks/useProfile";
export default function ReviewsPage() {
const { t } = useTranslation();
const navigate = useNavigate();
const { user, token, isAuthenticated, updateUser } = useAuthContext();
const { fetchProfile } = useProfile();
const [sidebarOpen, setSidebarOpen] = useState(false);
const [profileModalOpen, setProfileModalOpen] = useState(false);
const [authModalOpen, setAuthModalOpen] = useState(false);
const [rulesModalOpen, setRulesModalOpen] = useState(false);
const profileFetchedFor = useRef(null);
const profileData = useMemo(() => {
if (!user) return {};
const nameParts = (user.name || "").trim().split(/\s+/);
return {
login: user.login || "",
firstName: user.firstName || nameParts[0] || "",
lastName: user.lastName || nameParts.slice(1).join(" ") || "",
email: user.email || "",
phone: user.phone || "",
avatar: getImageUrl(user.avatar),
id: user.id,
};
}, [user]);
const {
reviews, loading, error, submitting, submitError,
loadReviews, submitReview, avgRating, distribution,
} = useReviews({
token,
isAuthenticated,
onAuthRequired: () => setAuthModalOpen(true),
authorFallbackName: profileData.firstName || t('reviews.you'),
});
useEffect(() => { loadReviews(); }, [loadReviews]);
// При загрузке страницы подтягиваем свежие данные профиля (аватар и т.д.)
// с бэка, чтобы навигация отражала изменения с других устройств, а не кэш.
// Дергаем один раз на пользователя.
useEffect(() => {
if (isAuthenticated && user?.id && profileFetchedFor.current !== user.id) {
profileFetchedFor.current = user.id;
fetchProfile(user.id).then(result => {
if (result.success) updateUser(result.data);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isAuthenticated, user?.id]);
// При открытии профиля подтягиваем свежие данные (аватар/профиль) с бэка,
// чтобы изменения с других устройств отражались, а не брались из кэша контекста
useEffect(() => {
if (profileModalOpen && isAuthenticated && user?.id) {
fetchProfile(user.id).then(result => {
if (result.success) updateUser(result.data);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [profileModalOpen, isAuthenticated, user?.id]);
return (
<div className="f-app">
<Sidebar
open={sidebarOpen}
activeNav="reviews"
onClose={() => setSidebarOpen(false)}
onNavClick={(key) => navigate("/feed")}
isAuthenticated={isAuthenticated}
onLoginClick={() => setAuthModalOpen(true)}
onRulesClick={() => setRulesModalOpen(true)}
onCreatePost={() => navigate("/feed")}
onProfileClick={() => setProfileModalOpen(true)}
profileData={profileData}
/>
<div className="f-main">
<AppTopBar
onMenuClick={() => setSidebarOpen((v) => !v)}
isAuthenticated={isAuthenticated}
profileData={profileData}
onProfileClick={() => setProfileModalOpen(true)}
onLoginClick={() => setAuthModalOpen(true)}
loginLabel={t('reviews.login')}
centerContent={<h1 className="rv-page-title">{t('reviews.title')}</h1>}
hideWordmark
/>
<div className="rv-layout">
<div className="rv-sidebar">
<RatingSummary
avgRating={avgRating}
totalCount={reviews.length}
distribution={distribution}
/>
{isAuthenticated ? (
<>
<ReviewForm onSubmit={submitReview} submitting={submitting} />
{submitError && (
<div className="rv-form-error">{t('reviews.submitError')}</div>
)}
</>
) : (
<div className="rv-login-prompt">
<p>{t('reviews.loginPrompt')}</p>
<button className="rv-login-btn" onClick={() => setAuthModalOpen(true)}>
{t('reviews.login')}
</button>
</div>
)}
</div>
<div className="rv-list">
{loading ? (
<div className="f-center">
<div className="f-spinner-wrap">
<div className="f-spinner" />
{t('reviews.loading')}
</div>
</div>
) : error && reviews.length === 0 ? (
<div className="f-center">
<div className="f-center-icon">💬</div>
<div className="f-center-title">{t('reviews.emptyTitle')}</div>
<div className="f-center-sub">{t('reviews.emptySub')}</div>
</div>
) : (
reviews.map((review) => <ReviewCard key={review.id} review={review} />)
)}
</div>
</div>
</div>
{profileModalOpen && isAuthenticated && (
<ProfileModal onClose={() => setProfileModalOpen(false)} initialData={profileData} />
)}
{authModalOpen && <AuthModal onClose={() => setAuthModalOpen(false)} />}
{rulesModalOpen && <RulesModal onClose={() => setRulesModalOpen(false)} />}
</div>
);
}