Skip to content

Commit a8661d5

Browse files
Completed reviews
1 parent 67b5649 commit a8661d5

27 files changed

Lines changed: 446 additions & 328 deletions

client/app/(auth)/login/login.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,10 @@ export default function LoginPage() {
3232

3333
const { mutate, isPending, isSuccess, isError } = useMutation({
3434
mutationFn: loginUser,
35-
// Update the onSuccess handler:
3635
onSuccess: (data) => {
3736
setSuccessMsg("Login successfully!");
3837
localStorage.setItem("token", data.token);
39-
setTokenState(data.token); // This will trigger verification in context
38+
setTokenState(data.token);
4039
reset();
4140
setTimeout(() => setIsLogin(false), 2000);
4241
},

client/app/(auth)/profile/profile.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ export default function Profile() {
9494
const location = await fetchLocationName(lat, lng);
9595
setValue("lat", lat);
9696
setValue("lng", lng);
97-
console.log("location",location)
9897
setValue("location", location || "Location found but could not get name");
9998
}
10099

client/app/_lib/context/contextAPI.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,10 @@ export default function Provider({ children }: { children: React.ReactNode }) {
3535
const [isSignUp, setIsSignUp] = useState(false);
3636
const [isProfile, setIsProfile] = useState(false);
3737
const [token, setTokenState] = useState('');
38-
3938
const toggleSidebar = () => setIsCollapsed((prev) => !prev);
4039

4140
useEffect(() => {
4241
const storedToken = localStorage.getItem('token');
43-
console.log("storedToken",storedToken)
4442
if (storedToken) setTokenState(storedToken);
4543
}, []);
4644

client/app/_lib/hooks/useLocation.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ export const useLocation = () => {
1717
return data?.display_name;
1818
} catch (err) {
1919
setError('Failed to fetch location name');
20-
console.error(err);
2120
return null;
2221
} finally {
2322
setIsLoading(false);

client/app/_lib/review/review.tsx

Lines changed: 146 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
'use client';
22

33
import 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

613
interface Review {
714
id: string;
@@ -17,29 +24,46 @@ interface Review {
1724
interface 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>
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
11
'use client'
22
import axios from 'axios';
3-
4-
const API_BASE = 'http://localhost:4000/api';
5-
63
export const authentication = async (token: string | null) => {
74
if (!token) return { success: false };
85

96
try {
10-
const response = await axios.get(`${API_BASE}/authenticate`, {
7+
const response = await axios.get(`${process.env.NEXT_PUBLIC_API_BASE}/authenticate`, {
118
headers: { Authorization: `Bearer ${token}` },
129
});
1310

1411
return {
1512
data: response.data
1613
};
1714
} catch (error) {
18-
console.error("Authentication failed:", error);
1915
return { success: false };
2016
}
2117
}

client/app/_lib/services/favoriteService.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { QueryFunctionContext } from '@tanstack/react-query';
22
import axios from 'axios';
33

4-
const API_BASE = 'http://localhost:4000/api';
54
export const addFavorite = async ({featureId, token} :{featureId:string, token:string}) => {
65

7-
const res = await fetch(`${API_BASE}/addFavorite`, {
6+
const res = await fetch(`${process.env.NEXT_PUBLIC_API_BASE}/addFavorite`, {
87
method: "POST",
98
headers: {
109
"Content-Type": "application/json",
@@ -15,7 +14,6 @@ export const addFavorite = async ({featureId, token} :{featureId:string, token:s
1514
}),
1615
});
1716

18-
console.log("res==>", res);
1917
if (!res.ok) {
2018
const error = await res.json();
2119
throw new Error(error.message || "Interval server error");
@@ -26,7 +24,7 @@ export const addFavorite = async ({featureId, token} :{featureId:string, token:s
2624

2725
export const removeFavorite = async ({featureId, token} :{featureId:string, token:string}) => {
2826
const response = await axios.post(
29-
`${API_BASE}/removeFavorite`,
27+
`${process.env.NEXT_PUBLIC_API_BASE}/removeFavorite`,
3028
{ featureId },
3129
{
3230
headers: {
@@ -41,7 +39,7 @@ export const findAllFavorites = async ({
4139
queryKey,
4240
}: QueryFunctionContext<[string, string]>) => {
4341
const [, token] = queryKey;
44-
const response = await axios.get(`${API_BASE}/findAllFavorites`, {
42+
const response = await axios.get(`${process.env.NEXT_PUBLIC_API_BASE}/findAllFavorites`, {
4543
headers: {
4644
Authorization: `Bearer ${token}`,
4745
},
@@ -53,11 +51,7 @@ export const findFavorite = async ({
5351
queryKey,
5452
}: QueryFunctionContext<[string, string, string]>) => {
5553
const [, token, id] = queryKey;
56-
57-
console.log("Frontend Token:", token);
58-
console.log("Feature ID:", id);
59-
60-
const response = await axios.get(`${API_BASE}/favorite?featureId=${id}`, {
54+
const response = await axios.get(`${process.env.NEXT_PUBLIC_API_BASE}/favorite?featureId=${id}`, {
6155
headers: {
6256
Authorization: `Bearer ${token}`,
6357
},

0 commit comments

Comments
 (0)