Страница профиля
Страница учетной записи
This commit is contained in:
parent
561a3426e0
commit
89bbc975ce
@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useParams, Link } from "react-router-dom";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { getUserProfile, getUserReviews } from "../services/supabase";
|
||||
import {
|
||||
FiEdit,
|
||||
FiSettings,
|
||||
@ -14,374 +15,156 @@ import ReviewCard from "../components/reviews/ReviewCard";
|
||||
import RatingChart from "../components/reviews/RatingChart";
|
||||
|
||||
function ProfilePage() {
|
||||
const { id } = useParams();
|
||||
const { currentUser, userProfile, logout } = useAuth();
|
||||
const { userId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { currentUser, userProfile } = useAuth();
|
||||
const [profile, setProfile] = useState(null);
|
||||
const [reviews, setReviews] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const [isCurrentUser, setIsCurrentUser] = useState(false);
|
||||
const [userData, setUserData] = useState(null);
|
||||
const [activeTab, setActiveTab] = useState("reviews");
|
||||
const [userReviews, setUserReviews] = useState([]);
|
||||
|
||||
// Check if this is the current user's profile
|
||||
useEffect(() => {
|
||||
if (currentUser && id === currentUser.uid) {
|
||||
setIsCurrentUser(true);
|
||||
setUserData(userProfile);
|
||||
} else {
|
||||
setIsCurrentUser(false);
|
||||
// In a real app, fetch the user data for the profile being viewed
|
||||
// For now, we'll use mock data
|
||||
setUserData({
|
||||
username: "Degradin",
|
||||
email: "degradin@campfiregg.ru",
|
||||
bio: "CEO of CampFireGG.",
|
||||
profilePicture:
|
||||
"https://staff.campfiregg.ru/assets/avatars/gdVPZMyCy9StMDMy.gif",
|
||||
createdAt: "2025-01-15T00:00:00.000Z",
|
||||
reviewCount: 28,
|
||||
isCritic: true,
|
||||
});
|
||||
}
|
||||
}, [currentUser, id, userProfile]);
|
||||
const loadProfile = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Если userId не указан, показываем профиль текущего пользователя
|
||||
const targetUserId = userId || currentUser?.id;
|
||||
if (!targetUserId) {
|
||||
navigate('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
// Mock loading reviews
|
||||
useEffect(() => {
|
||||
// In a real app, fetch user reviews from the database
|
||||
// For now, we'll use mock data
|
||||
const mockReviews = [
|
||||
{
|
||||
id: "1",
|
||||
user: {
|
||||
id: id,
|
||||
username: userData?.username || "User",
|
||||
profilePicture: userData?.profilePicture,
|
||||
isCritic: userData?.isCritic || false,
|
||||
},
|
||||
mediaId: "123",
|
||||
mediaType: "movie",
|
||||
mediaTitle: "The Last of Us",
|
||||
mediaPoster:
|
||||
"https://kinopoisk-ru.clstorage.net/1L67Lp105/810449redVB/ZukoaopfTiUSOnVYJJxkXkBCJLdbT63V4ifXLUdXiOvBUJT00q4PPtjWH9FFjkFNvMIv_JU9XtLv_pkyuD8UF7Osabtd2tgwV_qmziZcZBlH1_OkCM6_RCVxE3vRx4TmTdoPOVoixLZBSc4BVEkVmqkSP-pDL4lBKztFqCiuHg_kIK4mS4GJ5xwqOBon0e1e1gilv4NDYd31K1FnDBKGrwXfBPPQrMTxiUnJxRVQ35uk2bHhSKOA2SkyA6c1qldGKiFr5UNOhmBbq7FZZhsh21mK4f_MBidRt2GTY4lYAeADGg__U2RIfA5HT06X0JMZZ9E4qUsuAxrjvUNl9W8XQWfsoaTOQsfvVig_0mQXppyZxeB5hIsp1fyunOVB2kGkiJtCdVygC3kBhMdEFJ7flCgTN62K5UCR7vbBLzauFAArrmnvA4pGr1NtuN3j0CbanUzttwTHrdwzLVQqzZ8CZ4VWx_MZp8z1QoQATtVfWhQn2P9pQ29JE2Q3Bq92rpjK7-Eh6k1NTmWca3edbdQuWV9DaLfNz6Gbsukd70GewuJGEU333-IDeocHSIARUxFd4dizKopngh5rfMChdGiZwmempO9MActs1yp_UaeV5FIWAS76joOh1HPo3m5G1wjiBlzK8BUshzvPS05HlJpa0eOQeSoK64EbpfgAYvBnV8Ev4Oquxs4O6JSkPVPlUS8YmcNv9MPHJ5a4JpAkTVqKaIDQhrDRqov8QAeGDdAYlNnrWvhgQioC3mG3gu96IVtOpuVipcrODeGSrfhcr9mhHdEIIH5IyCWU9aOQY4URy62ImQ021-bCdE-CDohcUZTVYZP3LYwuxp-puAYq--FQjyokoONCAkvg3ak3maWcrN1ej-F5QYwuFHtl0KRPn8JnABuM-NssDr0JiYHJmJ8bkuhc-yiAoU9R6bBHYXaq1wnk76UsxIKIIh1gupDm0WVYlkymfA3LJxx07VgvytUOKksYzbvS7Mu6AQdFj1pYFh3oETglROdNnC53weF97BhMou2rbc9HCO-Tbb6T4pylkV1NoX5Nx2EdOySUIkiTBuqOmo38EuyMPEtCCsASV92Vq5v4L81mCBenc0bq-2ZcAm3kauyHQkZkVesw0KPeKxSTQGEyxcfgUrihFCUBG0Ogzl9FNBxrjPWDz0AO0BaTESYUNaHBIIvW7zuB4nnmXcjiYaGvTw7Bb1zvd5nun-ReWw3v84OHJ5gxq52lDthC6cAfibAQJ4e7h4bIBBEYUpoh0j6lymuLESq_xCV6LFGO5CKtrQbICGWc6XAbrh_mkNOAI3wOi-9Zf-EdrQDdj-rM3of6FWUAd08AT42ZENXQb9A4LAchz5fksAPju2ifjiNhpKKPSoCmkGew0qKbIVIai2vyAMxuWTDl0OYNVcvngJqO8h1kBbcAhEZAk1AeWetYOuAFJcoQY3yI6vxmWEXqp2Orz4iGqRwg_xrtFiCSmwDvt0yLJ13_LFnjhBFL6Ezfgr5XbY-1QAAKAJjbnN2u2j1sCi1N22t1gi00KBSI6K_r54zFgCRTqzEdpNBvnBlFoTlLgmwZdO8cJY8Xz2OMEUp-UK3KvE-Jx0YUV90c4hrzbMVlD9_u9AtveGGXD62s62eAAovqlq0w3GoYrpJQhCezhYym0r9pEOIFGIrqhFMHdVMtRbHHAcGI3xveXqtVPi_K5sbSIbOBJPgrWQ3s5-5qAIEG7hJsfZTuECdWGork-QqLKVOxqVsrwJ9FpEXXBj_dqET_yQnHghPTndIs0vKjDGoP3KE9zy90YZ_OrK8pKk0Njy-VbPCWLBtt21mPLrHBBO2QfaTWqwTbT2rIW4s1F20DdsvOxk_VWBQTIxC3owfjxt_lO0_psehYwWUkoedCCMZkVq-7lukVINZbDWA4i88iGn8g3mhNVcDtiJPNtFwrCzGAgwZN2NeSkKObOeoKbwqfJjUDLzokEY9jIWFtDwcI4pqgehmq2OMZnglvdMwNrpl_KFxjhBqCIgxew3rYYAwzwAmHzFIaV5QnG7HjjapJ1uS3iyLzYxXAp6UpbwZGjeGfYrle7RHhUpMHrrkEBaadMuQQJALfwaJAXkzzl63DuspBi0xbERecK5j2pAMhQt5j9YdtsaeWxCgmLWuHSUmhWqD1WaOZ4ViTQmY6BY1q2vtgnmUMWYZigNfG8JekRbkOC0JKk5zZWuHVcyMPI8cW7_cKInAkFsmmZGzti05L6B6icVAsmWEQGwtgeQxNodNxIBfmTNdBZUhYBb4bIkt9zghOhtoblBKjGvOjzaOKUe_7z6O-px8G7-RuLsAAh2md4XpVoZek3FkHrTcGgOaReuxZp83YQWCEF8I822uE-8eEyIiYmVEaL1-6aMmpxxmkMoBvNWNcj-egpWCEh8CpkOQ52SpdrZZSCuOyjAuvkvXsFOwNGoOshpvJPFluA75GAEMAX59cnanc_eQEK4HeoncJ5LDuHgXjZ6WnBMxLbJBhMljpl25U30VnOcLLr1p9LhjsydgPbc7XDnOdpI-7CQvCjZzY2thuUPrlwiKD0a06Cu9yLFaPLGTr5EiFxOFYonuUqR9hGV5Ka3TER2GeuSDWLQnVwWjBEQxzkahLt06Pw4VUURebKBf7K4QqyZus-0RsNibfCeAuIirCBggpWiu-HCvR4RxVSGC3hoBp2_ghmCKC2YGqhlBH9l3uwvbOzgbFkBgbGy9Sfa3JJ4kZJLeHqvHrE4lrbi3vCwiP7VeruBKhVSaeFo0ldk2A79ox59lnjRHHYs_aib_a78j7gkwOhR0Z1B1q0bikgCHBGSP1QiK7aBTHaOxsqAVMgS4VrLETItGsHJWKZz7MgiGbN-XRrcTVh6vN1kq_GmtEPIuIRkQbEZ_dYFSyIkctzhFhtUzt_aHUAudlparHD0vtH2o6FuTTJ93QSyi-ScytW_KtUOTJ2gesBtvCfdqrA7cJQY-B1Jzd0mjYfukN6sAaa0",
|
||||
content: "Клубничка имба.",
|
||||
ratings: {
|
||||
story: 9,
|
||||
visuals: 10,
|
||||
performance: 8,
|
||||
soundtrack: 10,
|
||||
enjoyment: 9,
|
||||
},
|
||||
likes: 42,
|
||||
comments: [],
|
||||
createdAt: "2025-05-20T14:30:00.000Z",
|
||||
spoiler: false,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
user: {
|
||||
id: id,
|
||||
username: userData?.username || "User",
|
||||
profilePicture: userData?.profilePicture,
|
||||
isCritic: userData?.isCritic || false,
|
||||
},
|
||||
mediaId: "456",
|
||||
mediaType: "tv",
|
||||
mediaTitle: "Breaking Bad",
|
||||
mediaPoster:
|
||||
"https://kinopoisk-ru.clstorage.net/1L67Lp105/810449redVB/ZukoaopfTiUSOnVYJJxkXkBCJLdbT63V4ifXLUdXiOvBUJT00q4PPtjWHZCFj0FO-VB7fVQ-X8cuLV6nbKsGUeY6fb1ODBjxRakyjfFJ8dnFgPEm3t24ROVjAHuQnEThzdrIfV1kBzXAh44BQhhVG2lV-CvFqZgWKvHFt_yhHgZqKiBrA8hObtUi8VirlSOdmsHoOYWDIZS_IRymRZ4NqcjRAvfTZED9wIBKQFvSVBai2_MgSaAAFCO3yqr46FfIa-_jY8zHySlUq7IZYVmmEhiFqHbAxa3W9eMYIw8ZQq8N2g50G-jNes4GwcLbntbVqlK7I8Qjw96s9whqPahQhaYlou7AhkYk1yU5G23e4dJXhC4-TcspnTrsnuuNWQYkjJeEe9-ryHUNjstBldJbmmhdv-IPYEjT53VCqfyrEcnk7ekjCAcGIZKiNpyqXqfW3ongvgFPIFK5JFFnyBrFKUESAj7YbEQ2DUnBgFRX25WjlXsrhWnKWSaywun8J9sNq6Xsbc8PzO6UbXYSrlYkFNEM4HcJQ2fTfW2U68naQOoGmAb4naPL844BD0RYHJJTo9k44o2ix1ZtdMzrOibThewg6WyDisWl1WW62O-cL12TSq00RUMoEnyuFeyAEECqSx8BMlMnirvCB8jBkVceUesTOGIBocKY6bQPbzXo0M2qbqjmTE9B5lsnuJorV2XSUQujeQHFatmy4F_nDZLKLMdTQfbVK000js-ChROZFlkhknIqwCIHXKY2guVz5F9Er-aj6kxMSOBS4rfQo9lgmppEKfIOx2iUN-AYL0bTDySO2MfwmWLFOwtHBwlamRrT6J0_5MTmRpSpskdks--eyG4k5WKACkmvVi1y2ubUoBUdhS77SszoGvGg3-XI2sWsDFkJ_FOuDH0AyEbNlBZdWieROuhLb4jUofMM6fhoGAalISrkAMmLb5-ge5mrHSXd3sIrtw3F4BV5KFRgghaI5w8YSvWTawt1jgwAD1Mb0lpqUjcsDW1FE2Z1jOK-p11Apy9r78VJRuGSpHid5t3sFt_FKfcBg-FasORZ4sQeSCKNWA01kyyNP8tDT0dc0FoZ4JI9pYwozppi8odkNOMTj67u6qfAB80nFCo73KRTadjSBWV_AQKu2TVkXqtMWwJiSJDFOxQsRPdGCYGFHZ7elG9fs6nKqw_SKjWP5rTi2QArJalvjIDGLhBtcpHlnCMQn4ukOUHFZFA_7d6kj1pLbAhcQTdYJwrzD4GLRBNfVZOpVLeigavIF65wSOV-7p8GaCmrJkpJzO4WavDRZZ7tmBJHK7RNDaUedW3WqoqXSGDJUgsyGqDGO4kGAswb2B_dq1I-b8vvTtmht44kOiCfwSsgpK_IwQ_imKo53eFZL1EZD6W6CoylUXGgnacC00UsjVsDOhulBnQND8_GWxOWWSNQ8m3P6slebTyHYzTnVAjt563vCscAbt_l8ZJsWO_Qkovg9k3FoZ64KZguRlNK4MhXT3ASLwQ0iUOPzdCREhyhV35ix20CVmQ2QKt6q5kK5W_hrEfBjSFUK_bbqhfhUtfFbvFEjuUVe2xeJUDXwSAGn4930moNOwCOyUFc0NNQYZl-rY8vBtPlvwLnMygeT-ZvYaCAykPkUio3FWLW7xsWQ-Q_SkQu3v1gma9PkkgoRNKEdFLlALOIiAeKENORGS5UPaIM5gscojVJZ3nmHA6tamwgA0dHYJNnf5FqXytRGICusE3Lr9A9K1BqyF0G6cDTzvrX5E69gI4NRtiQHZ6pmLFkgC8FnCx7QuszIN9O5a0sbY_OhueT6n1TYRWmEh1K5nvCD2wcMKbQroxXyGRMXsQwEqPHv0ePwIBTGdyRa92xbwnmBtgq-4QuuufQh24l4WKKh80kUKF9lm9YqxCfBG8xCcDmHrSuE-cC2E8khBhFe1SrgPQKT8KN3J9fEeBT-GKFKkYbJLdCpXaunoFr5WsvhUlL6F9g8tWim2TVmwsjdstMZR68LBguTZqAoEkWi_8frIK0gM5DBxFaW5Vg2_HlQGkP2aY_T2wxqtFF761pJsTMSO2do7WSa5kv2JXK7r7DRGFTcGBfqIjZAOxJmQKw0mMLvsjCww4aGlOZ45y2a8tiB17kMwAu9SnVymypbafLCAgoX--y3OOZJdjQAm2_S4gmmvTuHqYOnsAswBMBsNvlCHqCC8XGl9SVU64ZMWfJ58_S5r5P73ap2EQu6OurzApBbF1ru1PjGW1QmQQutotDLxC0Z53mgFnH5E_QTzxd68y6gQcJjxCZ3RFhmbGlSaqI0up7ziH1oBcNruoo4ILGwO8eYL7e7dyhEpXJYLxGBG0beCncZ49ZwigAF838FCRKsw2BB82SXNWdJNB6oUPnwJkjNAKqMeOeBeohZqQFgQDiGyMyVSfV6xmYh-U2zU1ulHhkl6dNmw4qjBzNfhGjDzKJCo8KlFFSG6eX9mzBoQefZr2JL7yhFAEtIaEkTgrF4p4os5btFimU1wNueA1Nphy6aJdjjxfPYsDbgrrbLwp9gosCydPXF9wrkPeqyKMIkCu-gu1-6Z7OLm_iaAeFSCpdYX_WZRlkFdgPI36Bg2LYtKZWo4LZym0G2YK21-sGOgaKCgFaGlSabJE57MDpQpHq8AGpdGAYAmSmLOKESYAo1KT3VKuZYR7aBOA8RosnmbXoWSiOmQgqR5IHepFiR7pHT0rFExbUnSkXv6HNqcAZpjPHbrmsmIkkqekris5EJVSi-d4vXuNdH0Eh90YNJlBzqRwnRt_AY81cTv2QaEr2xUcKSBLZ0tiq0rboy-HAHuT2TyQ6q9aKpuiuJc7Ah2dTq_hdq9Rh3hgDaXZEw2dWcaHWboKfCWHBn049FOSN_wEPy04akhLSL9gwL8fuyFyk-IBi82sTBS8hrOeNCkRtlSD9m6lfoJvZTOnzCk-nkzkgn2ONHw6qzBeM_dSjBn3Ixg6Bl9Ad2qMU-2UA4MNWY8",
|
||||
content:
|
||||
"Одно из величайших телевизионных шоу, когда-либо созданных. Развитие персонажа Уолтера Уайта не имеет себе равных, показывая его превращение из мягкосердечного учителя химии в безжалостного наркобарона. Брайан Крэнстон демонстрирует потрясающую игру, которую поддерживает не менее впечатляющий актерский состав. Сценарий неизменно превосходен, с плотным сюжетом и значимыми дугами персонажей.",
|
||||
ratings: {
|
||||
story: 10,
|
||||
visuals: 8,
|
||||
performance: 10,
|
||||
soundtrack: 7,
|
||||
enjoyment: 10,
|
||||
},
|
||||
likes: 87,
|
||||
comments: [{ id: "c1", user: "TVFan", content: "Completely agree!" }],
|
||||
createdAt: "2025-06-12T09:15:00.000Z",
|
||||
spoiler: true,
|
||||
},
|
||||
];
|
||||
const profileData = await getUserProfile(targetUserId);
|
||||
setProfile(profileData);
|
||||
|
||||
setUserReviews(mockReviews);
|
||||
}, [id, userData]);
|
||||
const reviewsData = await getUserReviews(targetUserId);
|
||||
setReviews(reviewsData);
|
||||
} catch (err) {
|
||||
setError("Ошибка при загрузке профиля");
|
||||
console.error("Error loading profile:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!userData) {
|
||||
loadProfile();
|
||||
}, [userId, currentUser, navigate]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="pt-20 flex justify-center items-center h-screen">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-campfire-amber"></div>
|
||||
<div className="pt-20 container-custom py-12">
|
||||
<div className="animate-pulse">
|
||||
<div className="h-32 bg-campfire-charcoal rounded-lg mb-6"></div>
|
||||
<div className="space-y-4">
|
||||
<div className="h-4 bg-campfire-charcoal rounded w-1/4"></div>
|
||||
<div className="h-4 bg-campfire-charcoal rounded w-1/2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate average ratings from reviews
|
||||
const calculateAverageRatings = () => {
|
||||
if (!userReviews.length) return null;
|
||||
if (error) {
|
||||
return (
|
||||
<div className="pt-20 container-custom py-12">
|
||||
<div className="bg-status-error bg-opacity-20 text-status-error p-6 rounded-lg">
|
||||
<h2 className="text-xl font-bold mb-2">Ошибка</h2>
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const totals = {
|
||||
story: 0,
|
||||
visuals: 0,
|
||||
performance: 0,
|
||||
soundtrack: 0,
|
||||
enjoyment: 0,
|
||||
};
|
||||
if (!profile) {
|
||||
return (
|
||||
<div className="pt-20 container-custom py-12">
|
||||
<div className="bg-status-error bg-opacity-20 text-status-error p-6 rounded-lg">
|
||||
<h2 className="text-xl font-bold mb-2">Профиль не найден</h2>
|
||||
<p>Пользователь с таким ID не существует.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
userReviews.forEach((review) => {
|
||||
Object.keys(totals).forEach((key) => {
|
||||
totals[key] += review.ratings[key];
|
||||
});
|
||||
});
|
||||
|
||||
const averages = {};
|
||||
Object.keys(totals).forEach((key) => {
|
||||
averages[key] = totals[key] / userReviews.length;
|
||||
});
|
||||
|
||||
return averages;
|
||||
};
|
||||
|
||||
const averageRatings = calculateAverageRatings();
|
||||
|
||||
// Format join date
|
||||
const joinDate = new Date(userData.createdAt).toLocaleDateString("ru-RU", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
const isOwnProfile = currentUser?.id === profile.id;
|
||||
|
||||
return (
|
||||
<div className="pt-20">
|
||||
{/* Profile Header */}
|
||||
<div className="bg-campfire-charcoal py-8">
|
||||
<div className="container-custom">
|
||||
<div className="flex flex-col md:flex-row items-center md:items-start gap-8">
|
||||
{/* Profile Picture */}
|
||||
<div className="relative">
|
||||
<div className="w-32 h-32 rounded-full overflow-hidden bg-campfire-dark">
|
||||
{userData.profilePicture ? (
|
||||
<img
|
||||
src={userData.profilePicture}
|
||||
alt={userData.username}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center text-campfire-ash">
|
||||
{userData.username.substring(0, 1).toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="pt-20 container-custom py-12">
|
||||
<div className="bg-campfire-charcoal rounded-lg overflow-hidden">
|
||||
{/* Заголовок профиля */}
|
||||
<div className="relative h-48 bg-gradient-to-r from-campfire-amber to-campfire-ember">
|
||||
{profile.profile_picture && (
|
||||
<img
|
||||
src={profile.profile_picture}
|
||||
alt={profile.username}
|
||||
className="absolute -bottom-16 left-8 w-32 h-32 rounded-full border-4 border-campfire-charcoal object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isCurrentUser && (
|
||||
<button className="absolute bottom-0 right-0 bg-campfire-amber text-campfire-dark p-2 rounded-full">
|
||||
<FiEdit size={16} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Profile Info */}
|
||||
<div className="flex-1 text-center md:text-left">
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-4">
|
||||
<h1 className="text-3xl font-bold">
|
||||
{userData.username}
|
||||
{userData.isCritic && (
|
||||
<span className="ml-2 inline-block px-2 py-0.5 text-xs font-medium bg-campfire-amber text-campfire-dark rounded-full">
|
||||
Резидент
|
||||
</span>
|
||||
)}
|
||||
</h1>
|
||||
|
||||
{isCurrentUser && (
|
||||
<div className="flex gap-2">
|
||||
<Link to="/settings" className="btn-secondary text-sm">
|
||||
<FiSettings className="mr-1" /> Настройки
|
||||
</Link>
|
||||
<button onClick={logout} className="btn-secondary text-sm">
|
||||
<FiLogOut className="mr-1" /> Выйти
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-campfire-ash my-4">
|
||||
{userData.bio ||
|
||||
`${userData.username} еще не написал ничего о себе.}`}
|
||||
<div className="pt-20 px-8 pb-8">
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold mb-2">{profile.username}</h1>
|
||||
<p className="text-campfire-ash">
|
||||
{profile.is_critic ? "Критик" : "Пользователь"}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center md:justify-start gap-4 text-sm text-campfire-ash">
|
||||
<div className="flex items-center">
|
||||
<FiCalendar className="mr-1" />
|
||||
<span>Участник с {joinDate}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<FiStar className="mr-1" />
|
||||
<span>{userData.reviewCount} Рецензий</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isOwnProfile && (
|
||||
<button
|
||||
onClick={() => navigate('/settings')}
|
||||
className="btn-secondary"
|
||||
>
|
||||
Редактировать профиль
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs Navigation */}
|
||||
<div className="bg-campfire-dark">
|
||||
<div className="container-custom">
|
||||
<div className="flex overflow-x-auto space-x-8">
|
||||
<button
|
||||
onClick={() => setActiveTab("reviews")}
|
||||
className={`py-4 font-medium ${
|
||||
activeTab === "reviews"
|
||||
? "text-campfire-amber border-b-2 border-campfire-amber"
|
||||
: "text-campfire-ash hover:text-campfire-light"
|
||||
}`}
|
||||
>
|
||||
Рецензии
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("stats")}
|
||||
className={`py-4 font-medium ${
|
||||
activeTab === "stats"
|
||||
? "text-campfire-amber border-b-2 border-campfire-amber"
|
||||
: "text-campfire-ash hover:text-campfire-light"
|
||||
}`}
|
||||
>
|
||||
Статистика
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("favorites")}
|
||||
className={`py-4 font-medium ${
|
||||
activeTab === "favorites"
|
||||
? "text-campfire-amber border-b-2 border-campfire-amber"
|
||||
: "text-campfire-ash hover:text-campfire-light"
|
||||
}`}
|
||||
>
|
||||
Избранное
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{profile.bio && (
|
||||
<div className="mb-8">
|
||||
<h2 className="text-xl font-bold mb-2">О себе</h2>
|
||||
<p className="text-campfire-light">{profile.bio}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className="container-custom py-8">
|
||||
{/* Reviews Tab */}
|
||||
{activeTab === "reviews" && (
|
||||
{/* Отзывы пользователя */}
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-6">
|
||||
Рецензии{" "}
|
||||
<span className="text-campfire-ash">({userReviews.length})</span>
|
||||
</h2>
|
||||
|
||||
{userReviews.length > 0 ? (
|
||||
<div className="space-y-8">
|
||||
{userReviews.map((review) => (
|
||||
<div key={review.id} className="mb-6">
|
||||
<div className="mb-2 flex items-center">
|
||||
<div className="w-8 h-12 mr-2">
|
||||
{review.mediaPoster ? (
|
||||
<img
|
||||
src={review.mediaPoster}
|
||||
alt={review.mediaTitle}
|
||||
className="w-full h-full object-cover rounded"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full bg-campfire-charcoal rounded"></div>
|
||||
)}
|
||||
<h2 className="text-2xl font-bold mb-4">Отзывы</h2>
|
||||
{reviews.length === 0 ? (
|
||||
<p className="text-campfire-ash">Пока нет отзывов</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{reviews.map((review) => (
|
||||
<div key={review.id} className="card p-4">
|
||||
<div className="flex items-center mb-4">
|
||||
<img
|
||||
src={review.media.poster_url}
|
||||
alt={review.media.title}
|
||||
className="w-16 h-24 object-cover rounded-lg mr-4"
|
||||
/>
|
||||
<div>
|
||||
<h3 className="font-bold">{review.media.title}</h3>
|
||||
<p className="text-sm text-campfire-ash">
|
||||
{review.media.type === 'movie' ? 'Фильм' :
|
||||
review.media.type === 'series' ? 'Сериал' : 'Игра'}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
to={`/media/${review.mediaId}?type=${review.mediaType}`}
|
||||
className="text-campfire-amber hover:text-campfire-ember"
|
||||
>
|
||||
{review.mediaTitle}
|
||||
</Link>
|
||||
<span className="mx-2 text-campfire-ash">•</span>
|
||||
<span className="text-campfire-ash">
|
||||
{review.mediaType === "movie" ? (
|
||||
<FiFilm className="inline" />
|
||||
) : (
|
||||
<FiTv className="inline" />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<ReviewCard review={review} isDetailed={true} />
|
||||
<p className="text-sm mb-4 line-clamp-3">{review.content}</p>
|
||||
<button
|
||||
onClick={() => navigate(`/media/${review.media_id}`)}
|
||||
className="btn-secondary w-full"
|
||||
>
|
||||
Читать полностью
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-campfire-charcoal rounded-lg p-8 text-center">
|
||||
<p className="text-campfire-ash">No reviews yet.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stats Tab */}
|
||||
{activeTab === "stats" && (
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-6">Статистика оценок</h2>
|
||||
|
||||
{averageRatings ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div className="bg-campfire-charcoal rounded-lg p-6">
|
||||
<h3 className="text-xl font-bold mb-4 text-center">
|
||||
Средние оценки
|
||||
</h3>
|
||||
<RatingChart ratings={averageRatings} showLegend={true} />
|
||||
</div>
|
||||
|
||||
<div className="bg-campfire-charcoal rounded-lg p-6">
|
||||
<h3 className="text-xl font-bold mb-4">Разбивка оценок</h3>
|
||||
<div className="space-y-4">
|
||||
{Object.entries(averageRatings).map(([category, value]) => (
|
||||
<div key={category}>
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<span className="capitalize">{category}</span>
|
||||
<span className="font-medium">
|
||||
{value.toFixed(1)}/10
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full h-2 bg-campfire-dark rounded-full">
|
||||
<div
|
||||
className="h-full bg-campfire-amber rounded-full"
|
||||
style={{ width: `${(value / 10) * 100}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-campfire-charcoal rounded-lg p-6 md:col-span-2">
|
||||
<h3 className="text-xl font-bold mb-4">Активность</h3>
|
||||
<div className="flex justify-center items-center h-48 text-campfire-ash">
|
||||
График активности скоро появится...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-campfire-charcoal rounded-lg p-8 text-center">
|
||||
<p className="text-campfire-ash">
|
||||
Статистика недоступна. Пишите рецензии, чтобы увидеть свою
|
||||
статистику.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Favorites Tab */}
|
||||
{activeTab === "favorites" && (
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-6">Favorites</h2>
|
||||
|
||||
<div className="bg-campfire-charcoal rounded-lg p-8 text-center">
|
||||
<p className="text-campfire-ash">
|
||||
{isCurrentUser
|
||||
? "Вы еще не добавили ни одного избранного. Отметьте медиа как избранное, чтобы видеть их здесь."
|
||||
: `${userData.username} еще не добавил ничего в избранное.`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user