added english language

This commit is contained in:
SlimusMinus
2026-07-09 00:09:45 +03:00
parent 3b503623f6
commit fffcdaa526
33 changed files with 454 additions and 687 deletions

View File

@@ -8,6 +8,7 @@ import ProfileAvatar from "../components/profile/ProfileAvatar";
import { ProfileModal } from "../components/profile";
import AuthModal from "../components/AuthModal";
import "../styles/reviews.css";
import { useTranslation } from 'react-i18next';
/* ── Star rating sub-component ── */
function StarRating({ value, onChange, size = 28, readOnly = false }) {
@@ -43,6 +44,7 @@ function StarRating({ value, onChange, size = 28, readOnly = false }) {
/* ── Single review card ── */
function ReviewCard({ review }) {
const { t } = useTranslation();
const date = review.createdAt
? new Date(review.createdAt).toLocaleDateString("ru-RU", {
day: "numeric", month: "long", year: "numeric",
@@ -56,7 +58,7 @@ function ReviewCard({ review }) {
{review.authorName ? review.authorName.charAt(0).toUpperCase() : "?"}
</div>
<div className="rv-card-meta">
<span className="rv-card-name">{review.authorName || "Аноним"}</span>
<span className="rv-card-name">{review.authorName || t('reviews.anonymous')}</span>
{date && <span className="rv-card-date">{date}</span>}
</div>
<div className="rv-card-rating">
@@ -71,6 +73,7 @@ function ReviewCard({ review }) {
/* ── Review form ── */
function ReviewForm({ onSubmit, submitting }) {
const { t } = useTranslation();
const [rating, setRating] = useState(0);
const [text, setText] = useState("");
const [error, setError] = useState("");
@@ -78,7 +81,7 @@ function ReviewForm({ onSubmit, submitting }) {
const handleSubmit = (e) => {
e.preventDefault();
if (rating === 0) {
setError("Поставьте оценку");
setError(t('reviews.ratePrompt'));
return;
}
setError("");
@@ -89,15 +92,15 @@ function ReviewForm({ onSubmit, submitting }) {
return (
<form className="rv-form" onSubmit={handleSubmit}>
<h3 className="rv-form-title">Оставить отзыв</h3>
<h3 className="rv-form-title">{t('reviews.leaveReview')}</h3>
<div className="rv-form-rating">
<span className="rv-form-label">Оценка:</span>
<span className="rv-form-label">{t('reviews.rating')}</span>
<StarRating value={rating} onChange={setRating} size={32} />
</div>
{error && <div className="rv-form-error">{error}</div>}
<textarea
className="rv-form-textarea"
placeholder="Напишите ваш отзыв (необязательно)..."
placeholder={t('reviews.reviewPlaceholder')}
value={text}
onChange={(e) => setText(e.target.value)}
maxLength={500}
@@ -106,7 +109,7 @@ function ReviewForm({ onSubmit, submitting }) {
<div className="rv-form-footer">
<span className="rv-form-counter">{text.length}/500</span>
<button className="rv-form-submit" type="submit" disabled={submitting}>
{submitting ? "Отправка..." : "Отправить"}
{submitting ? t('reviews.sending') : t('reviews.send')}
</button>
</div>
</form>
@@ -115,13 +118,14 @@ function ReviewForm({ onSubmit, submitting }) {
/* ── Main Reviews page ── */
export default function ReviewsPage() {
const { t } = useTranslation();
const navigate = useNavigate();
const { user, isAuthenticated } = useAuthContext();
const [sidebarOpen, setSidebarOpen] = useState(false);
const [profileModalOpen, setProfileModalOpen] = useState(false);
const [authModalOpen, setAuthModalOpen] = useState(false);
const [activeNav] = useState("Отзывы");
const [activeNav] = useState("reviews");
const [reviews, setReviews] = useState([]);
const [loading, setLoading] = useState(true);
@@ -149,7 +153,7 @@ export default function ReviewsPage() {
console.log('[Reviews] GET request to:', `${API_URL}/api/v1/reviews`);
const res = await fetch(`${API_URL}/api/v1/reviews`);
console.log('[Reviews] Response status:', res.status, res.ok);
if (!res.ok) throw new Error(`Ошибка ${res.status}`);
if (!res.ok) throw new Error(`Error ${res.status}`);
const data = await res.json();
console.log('[Reviews] Response data:', data);
const list = Array.isArray(data) ? data : data.items ?? data.reviews ?? [];
@@ -157,7 +161,7 @@ export default function ReviewsPage() {
setReviews(list);
} catch (err) {
console.error('[Reviews] Error:', err);
setError(err.message || "Не удалось загрузить отзывы");
setError(err.message || "Failed to load reviews");
// Show empty state with mock data hint
setReviews([]);
} finally {
@@ -190,7 +194,7 @@ export default function ReviewsPage() {
const responseData = await res.json();
console.log('[Reviews] POST response data:', responseData);
}
if (!res.ok) throw new Error(`Ошибка ${res.status}`);
if (!res.ok) throw new Error(`Error ${res.status}`);
// Reload reviews after successful submit
await loadReviews();
} catch (err) {
@@ -198,7 +202,7 @@ export default function ReviewsPage() {
setReviews((prev) => [
{
id: Date.now(),
authorName: profileData.firstName || "Вы",
authorName: profileData.firstName || t('reviews.you'),
rating,
text,
createdAt: new Date().toISOString(),
@@ -208,7 +212,7 @@ export default function ReviewsPage() {
} finally {
setSubmitting(false);
}
}, [isAuthenticated, loadReviews, profileData.firstName]);
}, [t, isAuthenticated, loadReviews, profileData.firstName]);
/** Average rating */
const avgRating = reviews.length
@@ -245,7 +249,7 @@ export default function ReviewsPage() {
<MenuIcon />
</button>
<div className="f-topbar-center">
<h1 className="rv-page-title">Отзывы</h1>
<h1 className="rv-page-title">{t('reviews.title')}</h1>
</div>
{/* Иконка профиля в правом углу */}
{isAuthenticated ? (
@@ -254,7 +258,7 @@ export default function ReviewsPage() {
</div>
) : (
<button className="f-login-btn" onClick={() => setAuthModalOpen(true)}>
Войти
{t('reviews.login')}
</button>
)}
</div>
@@ -271,11 +275,11 @@ export default function ReviewsPage() {
<StarRating value={Math.round(Number(avgRating))} readOnly size={22} />
</>
) : (
<span className="rv-summary-none">Пока нет отзывов</span>
<span className="rv-summary-none">{t('reviews.noReviews')}</span>
)}
</div>
<span className="rv-summary-total">
{reviews.length} {reviews.length === 1 ? "отзыв" : reviews.length < 5 ? "отзыва" : "отзывов"}
{t('reviews.title')} ({reviews.length})
</span>
{/* Distribution bars */}
@@ -299,9 +303,9 @@ export default function ReviewsPage() {
<ReviewForm onSubmit={handleSubmitReview} submitting={submitting} />
) : (
<div className="rv-login-prompt">
<p>Войдите, чтобы оставить отзыв</p>
<p>{t('reviews.loginPrompt')}</p>
<button className="rv-login-btn" onClick={() => setAuthModalOpen(true)}>
Войти
{t('reviews.login')}
</button>
</div>
)}
@@ -313,14 +317,14 @@ export default function ReviewsPage() {
<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">Отзывов пока нет</div>
<div className="f-center-sub">Станьте первым — оставьте отзыв о площадке</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} />)