From c2f4eddb5589fd10506bcf6f037fbcf2fb22d809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Gierwia=C5=82o?= Date: Fri, 14 Nov 2025 22:48:30 +0100 Subject: [PATCH] feat: display user ratings on public profiles and add profile links - Add comprehensive ratings section to PublicProfilePage showing average rating, individual reviews with comments, and collaboration preferences - Make partner avatars and names clickable in MatchesPage and MatchChatPage to navigate to their public profiles - Add hover effects on profile links for better UX - Fetch and display ratings using ratingsAPI endpoint --- frontend/src/pages/MatchChatPage.jsx | 26 +++-- frontend/src/pages/MatchesPage.jsx | 26 +++-- frontend/src/pages/PublicProfilePage.jsx | 136 ++++++++++++++++++++++- 3 files changed, 163 insertions(+), 25 deletions(-) diff --git a/frontend/src/pages/MatchChatPage.jsx b/frontend/src/pages/MatchChatPage.jsx index c888a51..7c2a2b0 100644 --- a/frontend/src/pages/MatchChatPage.jsx +++ b/frontend/src/pages/MatchChatPage.jsx @@ -1,5 +1,5 @@ import { useState, useRef, useEffect } from 'react'; -import { useParams, useNavigate } from 'react-router-dom'; +import { useParams, useNavigate, Link } from 'react-router-dom'; import Layout from '../components/layout/Layout'; import { useAuth } from '../contexts/AuthContext'; import { matchesAPI } from '../services/api'; @@ -252,17 +252,21 @@ const MatchChatPage = () => {
- {partner.username} + + {partner.username} +
-

- {partner.firstName && partner.lastName - ? `${partner.firstName} ${partner.lastName}` - : partner.username} -

+ +

+ {partner.firstName && partner.lastName + ? `${partner.firstName} ${partner.lastName}` + : partner.username} +

+

@{partner.username}

diff --git a/frontend/src/pages/MatchesPage.jsx b/frontend/src/pages/MatchesPage.jsx index 4761442..189a200 100644 --- a/frontend/src/pages/MatchesPage.jsx +++ b/frontend/src/pages/MatchesPage.jsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, Link } from 'react-router-dom'; import Layout from '../components/layout/Layout'; import { useAuth } from '../contexts/AuthContext'; import { matchesAPI } from '../services/api'; @@ -246,17 +246,21 @@ const MatchCard = ({ match, onAccept, onReject, onOpenChat, processing }) => {
- {match.partner.username} + + {match.partner.username} +
-

- {match.partner.firstName && match.partner.lastName - ? `${match.partner.firstName} ${match.partner.lastName}` - : match.partner.username} -

+ +

+ {match.partner.firstName && match.partner.lastName + ? `${match.partner.firstName} ${match.partner.lastName}` + : match.partner.username} +

+

@{match.partner.username}

diff --git a/frontend/src/pages/PublicProfilePage.jsx b/frontend/src/pages/PublicProfilePage.jsx index 402fb8d..3b6b5ed 100644 --- a/frontend/src/pages/PublicProfilePage.jsx +++ b/frontend/src/pages/PublicProfilePage.jsx @@ -1,14 +1,16 @@ import { useState, useEffect } from 'react'; import { useParams, Link } from 'react-router-dom'; -import { authAPI } from '../services/api'; +import { authAPI, ratingsAPI } from '../services/api'; import Layout from '../components/layout/Layout'; -import { User, MapPin, Globe, Hash, Youtube, Instagram, Facebook, Award, Users, Star, Calendar, Loader2, AlertCircle } from 'lucide-react'; +import { User, MapPin, Globe, Hash, Youtube, Instagram, Facebook, Award, Users, Star, Calendar, Loader2, AlertCircle, ThumbsUp } from 'lucide-react'; const PublicProfilePage = () => { const { username } = useParams(); const [profile, setProfile] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); + const [ratings, setRatings] = useState(null); + const [ratingsLoading, setRatingsLoading] = useState(true); useEffect(() => { const fetchProfile = async () => { @@ -26,6 +28,22 @@ const PublicProfilePage = () => { fetchProfile(); }, [username]); + useEffect(() => { + const fetchRatings = async () => { + try { + setRatingsLoading(true); + const data = await ratingsAPI.getUserRatings(username); + setRatings(data.data); + } catch (err) { + console.error('Failed to load ratings:', err); + } finally { + setRatingsLoading(false); + } + }; + + fetchRatings(); + }, [username]); + if (loading) { return ( @@ -150,7 +168,7 @@ const PublicProfilePage = () => { {/* Social Media Links */} {(profile.youtubeUrl || profile.instagramUrl || profile.facebookUrl || profile.tiktokUrl) && ( -
+

Social Media

{profile.youtubeUrl && ( @@ -202,6 +220,118 @@ const PublicProfilePage = () => {
)} + + {/* Ratings Section */} +
+

+ + Reviews +

+ + {ratingsLoading ? ( +
+ +
+ ) : ratings && ratings.ratings.length > 0 ? ( +
+ {/* Summary */} +
+
+ {ratings.averageRating} +
+ {[1, 2, 3, 4, 5].map((star) => ( + + ))} +
+
+ + Based on {ratings.ratingsCount} {ratings.ratingsCount === 1 ? 'review' : 'reviews'} + +
+ + {/* Individual Ratings */} +
+ {ratings.ratings.map((rating) => ( +
+
+ {/* Rater Info */} + + {rating.rater.username} + + +
+ {/* Rater Name and Stars */} +
+
+ + {rating.rater.firstName && rating.rater.lastName + ? `${rating.rater.firstName} ${rating.rater.lastName}` + : rating.rater.username} + +

@{rating.rater.username}

+
+
+ {[1, 2, 3, 4, 5].map((star) => ( + + ))} +
+
+ + {/* Comment */} + {rating.comment && ( +

{rating.comment}

+ )} + + {/* Would Collaborate Again */} + {rating.wouldCollaborateAgain && ( +
+ + Would collaborate again +
+ )} + + {/* Event and Date */} +
+ + {rating.event.name} + + + {new Date(rating.createdAt).toLocaleDateString()} +
+
+
+
+ ))} +
+
+ ) : ( +

No reviews yet

+ )} +