11'use client' ;
22
33import ReactStars from 'react-stars'
4- import { useEffect , useState } from 'react' ;
4+ import { useEffect , useRef , useState } from 'react' ;
5+ import { MoreVertical } from 'lucide-react' ;
6+ import { getReview } from '../services/reviewService' ;
7+ import { useContextAPI } from '../context/contextAPI' ;
8+ import { getUserProfile } from '../services/userService' ;
9+ import { useQuery } from '@tanstack/react-query' ;
10+ import Loader from '@/app/components/specialized/Loader' ;
11+ import ErrorMessage from '@/app/components/specialized/ErrorMessage' ;
512
613interface Review {
714 id : string ;
@@ -17,29 +24,46 @@ interface Review {
1724interface ReviewSectionProps {
1825 reviews : Review [ ] ;
1926 averageRating : number ;
20- userId ?: string ;
2127 featureId : string ;
2228 token :string ;
2329 onReviewSubmit : ( rating : number , comment : string ) => Promise < void > ;
30+ onReviewRemove :( id :string ) => Promise < void > ,
31+ onReviewUpdate :( rating : number , editingReviewId :string , reviewText : string ) => Promise < void > ;
2432}
2533
26- export function ReviewSection ( {
34+ export default function ReviewSection ( {
2735 reviews,
2836 averageRating,
2937 featureId,
38+ onReviewRemove,
3039 onReviewSubmit,
31- token
40+ onReviewUpdate,
41+ token,
3242} : ReviewSectionProps ) {
43+ const { setIsLogin} = useContextAPI ( )
3344 const [ rating , setRating ] = useState < number > ( 1 ) ;
3445 const [ reviewText , setReviewText ] = useState ( '' ) ;
3546 const [ showReviewForm , setShowReviewForm ] = useState ( false ) ;
3647 const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
37- const [ domLoaded , setDomLoaded ] = useState ( false ) ;
38- useEffect ( ( ) => {
48+ const [ domLoaded , setDomLoaded ] = useState ( false ) ;
49+ const [ open , setOpen ] = useState ( false ) ;
50+ const [ editingReviewId , setEditingReviewId ] = useState < string | null > ( null ) ;
51+ const menuRef = useRef < HTMLDivElement | null > ( null ) ;
52+
53+ const { data, isLoading, isError, error , refetch} = useQuery ( {
54+ queryKey : [ "user" , token ] ,
55+ queryFn : getUserProfile ,
56+ enabled : ! ! token ,
57+ } ) ;
58+
59+ useEffect ( ( ) => {
3960 setDomLoaded ( true ) ;
4061 } , [ ] )
41- console . log ( "|reviews" , reviews )
42- const handleSubmit = async ( ) => {
62+
63+ const handleSubmit = async ( ) => {
64+ if ( ! token ) {
65+ return setIsLogin ( true )
66+ }
4367 if ( rating < 1 || rating > 5 ) return ;
4468
4569 setIsSubmitting ( true ) ;
@@ -53,39 +77,61 @@ export function ReviewSection({
5377 }
5478 } ;
5579
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- }
80+ const handleRemove = ( reviewId :string ) => {
81+ onReviewRemove ( reviewId ) ;
82+ setOpen ( false ) ;
6883 } ;
6984
70- const handleRemove = async ( ) => {
71- if ( rating === 0 ) return ;
72-
73- setIsSubmitting ( true ) ;
74- try {
75- await onReviewSubmit ( rating , reviewText ) ;
76- setRating ( 0 ) ;
85+ const handleUpdate = async ( ) => {
86+ if ( ! editingReviewId ) return ;
87+ await onReviewUpdate ( rating , editingReviewId , reviewText ) ;
88+ setEditingReviewId ( null ) ;
7789 setReviewText ( '' ) ;
90+ setRating ( 1 ) ;
7891 setShowReviewForm ( false ) ;
79- } finally {
80- setIsSubmitting ( false ) ;
81- }
92+
8293 } ;
8394
8495 const ratingChanged = ( newRating :number ) => {
85- console . log ( newRating )
8696 setRating ( newRating )
8797}
8898
99+ // Fetch review data for editing
100+ const fetchUpdateReview = async ( reviewId : string ) => {
101+ try {
102+ const res = await getReview ( { reviewId, token } ) ;
103+ setRating ( res ?. reviews . rating ) ;
104+ setReviewText ( res ?. reviews . comment ) ;
105+ setEditingReviewId ( reviewId ) ;
106+ setShowReviewForm ( true ) ;
107+ setOpen ( false ) ;
108+ } catch ( error ) {
109+ console . error ( 'Failed to fetch review for update' , error ) ;
110+ }
111+ } ;
112+ // Close dropdown if clicked outside
113+ useEffect ( ( ) => {
114+ const handler = ( e :MouseEvent ) => {
115+ if ( menuRef . current && ! menuRef . current . contains ( e . target as Node ) ) {
116+ setOpen ( false ) ;
117+ }
118+ } ;
119+ document . addEventListener ( "mousedown" , handler ) ;
120+ return ( ) => document . removeEventListener ( "mousedown" , handler ) ;
121+ } , [ ] ) ;
122+
123+ if ( isLoading ) return < Loader />
124+
125+ if ( isError ) {
126+ return (
127+ < ErrorMessage
128+ message = { error ?. message || 'Failed to load User.' }
129+ onRetry = { ( ) => refetch ( ) }
130+ />
131+ )
132+ }
133+
134+
89135 return (
90136 < div className = "mt-12" >
91137 < div className = "flex justify-between items-center mb-6" >
@@ -118,23 +164,30 @@ export function ReviewSection({
118164 { /* Review Form */ }
119165 { showReviewForm && (
120166 < 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-
167+ < h3 className = "text-xl font-semibold text-gray-900 mb-4" >
168+ { editingReviewId ? 'Update Your Review' : 'Share Your Experience' }
169+ </ h3 >
170+
123171 < div className = "mb-4" >
124172 < label className = "block text-sm font-medium text-gray-700 mb-2" > Your Rating</ label >
125173 < 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-
174+ { domLoaded && (
175+ < ReactStars
176+ count = { 5 }
177+ value = { rating }
178+ onChange = { ratingChanged }
179+ size = { 24 }
180+ color2 = { '#ffd700' }
181+ />
182+ ) }
133183 </ div >
134184 </ div >
135185
136186 < div className = "mb-4" >
137- < label htmlFor = "review" className = "block text-sm font-medium text-gray-700 mb-2" >
187+ < label
188+ htmlFor = "review"
189+ className = "block text-sm font-medium text-gray-700 mb-2"
190+ >
138191 Your Review
139192 </ label >
140193 < textarea
@@ -145,25 +198,38 @@ export function ReviewSection({
145198 value = { reviewText }
146199 onChange = { ( e ) => setReviewText ( e . target . value ) }
147200 placeholder = "Share details about your experience..."
201+ required
148202 />
149203 </ 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 >
204+ { editingReviewId ? (
205+ < button
206+ type = "button"
207+ onClick = { handleUpdate }
208+ disabled = { isSubmitting || rating === 0 }
209+ className = "px-4 py-2 bg-green-600 cursor-pointer text-white rounded-lg hover:bg-green-700 disabled:bg-green-300 transition-colors"
210+ >
211+ { isSubmitting ? 'Updating...' : 'Update Review' }
212+ </ button >
213+ ) : (
214+ < button
215+ type = "button"
216+ onClick = { handleSubmit }
217+ disabled = { isSubmitting || rating === 0 }
218+ className = "px-4 py-2 bg-green-600 cursor-pointer text-white rounded-lg hover:bg-green-700 disabled:bg-green-300 transition-colors"
219+ >
220+ { isSubmitting ? 'Submitting...' : 'Submit Review' }
221+ </ button >
222+ ) }
159223 </ div >
160224 ) }
161225
162226 { /* Reviews List */ }
163227 < div className = "space-y-8" >
164228 { reviews . length > 0 ? (
165- reviews . map ( ( review :any ) => (
166- < div key = { review . id } className = "border-b border-gray-200 pb-8 last:border-0" >
229+ reviews . map ( ( review :any ) => {
230+ return (
231+ < div className = "flex justify-between items-center" key = { review . id } >
232+ < div className = "border-b border-gray-200 pb-8 last:border-0" >
167233 < div className = "flex items-center mb-3" >
168234 < div className = "flex mr-2" >
169235 { domLoaded && < ReactStars
@@ -184,8 +250,34 @@ export function ReviewSection({
184250
185251 < p className = "text-gray-800 mb-2" > { review ?. comment } </ p >
186252 </ div >
187- ) )
188- ) : (
253+ { data ?. user . id == review ?. userId ? < div className = "relative" ref = { menuRef } >
254+ < button
255+ onClick = { ( ) => setOpen ( ! open ) }
256+ className = "p-2 rounded-full hover:bg-gray-200 focus:outline-none"
257+ >
258+ < MoreVertical className = "w-5 h-5" />
259+ </ button >
260+ { open && (
261+ < div className = "absolute right-0 mt-2 w-20 cursor-pointer bg-white rounded-md shadow-lg py-1 z-50" >
262+ < button
263+ onClick = { ( ) => fetchUpdateReview ( review ?. id ) }
264+ className = " px-4 cursor-pointer py-2 text-sm text-gray-700 hover:bg-gray-100 w-full"
265+ >
266+ Update
267+ </ button >
268+ < button
269+ onClick = { ( ) => handleRemove ( review ?. id ) }
270+ className = " px-4 py-2 cursor-pointer text-sm text-gray-700 hover:bg-gray-100 w-full"
271+ >
272+ Delete
273+ </ button >
274+ </ div >
275+ ) }
276+ </ div > :'' }
277+ </ div >
278+ )
279+ } )
280+ ) : (
189281 < div className = "text-left py-8" >
190282 < p className = "text-gray-500 text-lg" > No reviews yet. Be the first to review!</ p >
191283 </ div >
0 commit comments