160 lines
6.4 KiB
JavaScript
160 lines
6.4 KiB
JavaScript
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>
|
||
);
|
||
} |