1+ 'use client' ;
2+
3+ import ReactStars from 'react-stars'
4+ import { useEffect , useState } from 'react' ;
5+
6+ interface Review {
7+ id : string ;
8+ rating : number ;
9+ comment : string ;
10+ createdAt : string ;
11+ user :{
12+ email :string ;
13+ name : string ;
14+ }
15+ }
16+
17+ interface ReviewSectionProps {
18+ reviews : Review [ ] ;
19+ averageRating : number ;
20+ userId ?: string ;
21+ featureId : string ;
22+ token :string ;
23+ onReviewSubmit : ( rating : number , comment : string ) => Promise < void > ;
24+ }
25+
26+ export function ReviewSection ( {
27+ reviews,
28+ averageRating,
29+ featureId,
30+ onReviewSubmit,
31+ token
32+ } : ReviewSectionProps ) {
33+ const [ rating , setRating ] = useState < number > ( 1 ) ;
34+ const [ reviewText , setReviewText ] = useState ( '' ) ;
35+ const [ showReviewForm , setShowReviewForm ] = useState ( false ) ;
36+ const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
37+ const [ domLoaded , setDomLoaded ] = useState ( false ) ;
38+ useEffect ( ( ) => {
39+ setDomLoaded ( true ) ;
40+ } , [ ] )
41+ console . log ( "|reviews" , reviews )
42+ const handleSubmit = async ( ) => {
43+ if ( rating < 1 || rating > 5 ) return ;
44+
45+ setIsSubmitting ( true ) ;
46+ try {
47+ await onReviewSubmit ( Math . round ( rating ) , reviewText ) ;
48+ setRating ( 0 ) ;
49+ setReviewText ( '' ) ;
50+ setShowReviewForm ( false ) ;
51+ } finally {
52+ setIsSubmitting ( false ) ;
53+ }
54+ } ;
55+
56+ const handleUpdate = async ( ) => {
57+ if ( rating === 0 ) return ;
58+
59+ setIsSubmitting ( true ) ;
60+ try {
61+ await onReviewSubmit ( rating , reviewText ) ;
62+ setRating ( 0 ) ;
63+ setReviewText ( '' ) ;
64+ setShowReviewForm ( false ) ;
65+ } finally {
66+ setIsSubmitting ( false ) ;
67+ }
68+ } ;
69+
70+ const handleRemove = async ( ) => {
71+ if ( rating === 0 ) return ;
72+
73+ setIsSubmitting ( true ) ;
74+ try {
75+ await onReviewSubmit ( rating , reviewText ) ;
76+ setRating ( 0 ) ;
77+ setReviewText ( '' ) ;
78+ setShowReviewForm ( false ) ;
79+ } finally {
80+ setIsSubmitting ( false ) ;
81+ }
82+ } ;
83+
84+ const ratingChanged = ( newRating :number ) => {
85+ console . log ( newRating )
86+ setRating ( newRating )
87+ }
88+
89+ return (
90+ < div className = "mt-12" >
91+ < div className = "flex justify-between items-center mb-6" >
92+ < h2 className = "text-2xl font-bold text-gray-700" > Customer Reviews</ h2 >
93+ { token && (
94+ < button
95+ onClick = { ( ) => setShowReviewForm ( ! showReviewForm ) }
96+ className = "px-4 py-2 cursor-pointer bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
97+ >
98+ { showReviewForm ? 'Cancel' : 'Write a Review' }
99+ </ button >
100+ ) }
101+ </ div >
102+
103+ { /* Rating Summary */ }
104+ < div className = "flex items-center mb-8 py-2 border-b-1 border-gray-300" >
105+ < div className = "flex mr-2" >
106+ { domLoaded && < ReactStars
107+ edit = { false }
108+ count = { 5 }
109+ size = { 24 }
110+ value = { averageRating }
111+ color2 = { '#ffd700' } /> }
112+ </ div >
113+ < span className = "text-lg text-gray-600" >
114+ { averageRating . toFixed ( 1 ) } out of 5 ({ reviews . length } reviews)
115+ </ span >
116+ </ div >
117+
118+ { /* Review Form */ }
119+ { showReviewForm && (
120+ < div className = "bg-gray-50 p-6 rounded-lg mb-8 shadow-sm" >
121+ < h3 className = "text-xl font-semibold text-gray-900 mb-4" > Share Your Experience</ h3 >
122+
123+ < div className = "mb-4" >
124+ < label className = "block text-sm font-medium text-gray-700 mb-2" > Your Rating</ label >
125+ < div className = "flex space-x-1" >
126+ { domLoaded && < ReactStars
127+ count = { 5 }
128+ value = { rating }
129+ onChange = { ratingChanged }
130+ size = { 24 }
131+ color2 = { '#ffd700' } /> }
132+
133+ </ div >
134+ </ div >
135+
136+ < div className = "mb-4" >
137+ < label htmlFor = "review" className = "block text-sm font-medium text-gray-700 mb-2" >
138+ Your Review
139+ </ label >
140+ < textarea
141+ id = "review"
142+ rows = { 4 }
143+ className = "w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none
144+ focus:ring-green-600 focus:border-green-500"
145+ value = { reviewText }
146+ onChange = { ( e ) => setReviewText ( e . target . value ) }
147+ placeholder = "Share details about your experience..."
148+ />
149+ </ div >
150+
151+ < button
152+ type = "button"
153+ onClick = { handleSubmit }
154+ disabled = { isSubmitting || rating === 0 }
155+ className = "px-4 py-2 bg-green-600 cursor-pointer text-white rounded-lg hover:bg-green-700 disabled:bg-green-300 transition-colors"
156+ >
157+ { isSubmitting ? 'Submitting...' : 'Submit Review' }
158+ </ button >
159+ </ div >
160+ ) }
161+
162+ { /* Reviews List */ }
163+ < div className = "space-y-8" >
164+ { reviews . length > 0 ? (
165+ reviews . map ( ( review :any ) => (
166+ < div key = { review . id } className = "border-b border-gray-200 pb-8 last:border-0" >
167+ < div className = "flex items-center mb-3" >
168+ < div className = "flex mr-2" >
169+ { domLoaded && < ReactStars
170+ count = { 5 }
171+ value = { review ?. rating }
172+ onChange = { ratingChanged }
173+ size = { 24 }
174+ color2 = { '#ffd700' } /> }
175+ </ div >
176+ < span className = "text-sm text-gray-500" >
177+ { new Date ( review . createdAt ) . toLocaleDateString ( 'en-US' , {
178+ year : 'numeric' ,
179+ month : 'long' ,
180+ day : 'numeric'
181+ } ) }
182+ </ span >
183+ </ div >
184+
185+ < p className = "text-gray-800 mb-2" > { review ?. comment } </ p >
186+ </ div >
187+ ) )
188+ ) : (
189+ < div className = "text-left py-8" >
190+ < p className = "text-gray-500 text-lg" > No reviews yet. Be the first to review!</ p >
191+ </ div >
192+ ) }
193+ </ div >
194+ </ div >
195+ ) ;
196+ }
0 commit comments