Skip to content

Commit 46a69e9

Browse files
committed
finished reviews section
1 parent 55f95a7 commit 46a69e9

File tree

7 files changed

+260
-12
lines changed

7 files changed

+260
-12
lines changed

Diff for: backend/src/models/review.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import { InferSchemaType, model, Schema } from "mongoose";
22

3-
const reviewSchema = new Schema({
3+
const reviewSchema = new Schema(
4+
{
45
// _id: { type: String, required: true },
56
review_id: { type: Number, required: true },
6-
userID: { type: Number, required: true },
7+
userID: { type: String, required: true },
78
tradespersonID: { type: Number, required: true },
89
rating: { type: Number, required: true },
910
comment: { type: String, required: true },
1011
date: { type: String, required: true },
11-
}, { timestamps: true });
12+
},
13+
{ timestamps: true }
14+
);
1215

1316
type Review = InferSchemaType<typeof reviewSchema>;
1417

1518
// reviewSchema.set('validateBeforeSave', false);
1619

17-
1820
export default model<Review>("reviews", reviewSchema);

Diff for: frontend/package-lock.json

+61-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: frontend/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"axios": "^1.6.5",
1515
"react": "^18.2.0",
1616
"react-dom": "^18.2.0",
17+
"react-icons": "^5.0.1",
18+
"react-modal": "^3.16.1",
1719
"react-router-dom": "^6.21.3",
1820
"react-social-icons": "^6.9.0"
1921
},

Diff for: frontend/src/components/ReviewModal.tsx

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React, { useState } from "react";
2+
import { FaStar } from "react-icons/fa"; // if using react-icons
3+
4+
const ReviewModal = ({ workerName, isOpen, onSubmit, onCancel }) => {
5+
const [rating, setRating] = useState(null);
6+
const [hover, setHover] = useState(null); // For hover state on stars
7+
const [comment, setComment] = useState("");
8+
9+
if (!isOpen) return null;
10+
11+
return (
12+
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full">
13+
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
14+
<div className="mt-3 text-center">
15+
<h3 className="text-lg leading-6 font-medium text-gray-900">
16+
Leave a review for {workerName}
17+
</h3>
18+
<div className="mt-2">
19+
<div className="flex justify-center">
20+
{[...Array(5)].map((star, index) => {
21+
const ratingValue = index + 1;
22+
return (
23+
<label key={index}>
24+
<input
25+
type="radio"
26+
name="rating"
27+
value={ratingValue}
28+
onClick={() => setRating(ratingValue)}
29+
className="hidden"
30+
/>
31+
<FaStar
32+
size={24}
33+
className="cursor-pointer"
34+
color={
35+
ratingValue <= (hover || rating) ? "#ffc107" : "#e4e5e9"
36+
}
37+
onMouseEnter={() => setHover(ratingValue)}
38+
onMouseLeave={() => setHover(null)}
39+
/>
40+
</label>
41+
);
42+
})}
43+
</div>
44+
<textarea
45+
rows="4"
46+
className="mt-2 px-4 py-2 border rounded-md w-full"
47+
placeholder="Share details of your own experience..."
48+
value={comment}
49+
onChange={(e) => setComment(e.target.value)}
50+
></textarea>
51+
</div>
52+
<div className="items-center px-4 py-3">
53+
<button
54+
id="cancel"
55+
className="px-4 py-2 bg-gray-200 text-gray-900 rounded-md"
56+
onClick={onCancel}
57+
>
58+
Cancel
59+
</button>
60+
<button
61+
id="submit"
62+
className="mx-3 px-4 py-2 bg-blue-500 text-white rounded-md focus:outline-none"
63+
onClick={() => onSubmit({ rating, comment })}
64+
>
65+
Submit
66+
</button>
67+
</div>
68+
</div>
69+
</div>
70+
</div>
71+
);
72+
};
73+
74+
export default ReviewModal;

Diff for: frontend/src/components/SearchBar.tsx

+17-6
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,37 @@
1-
import React from "react";
2-
import searchIcon from "../assets/search.svg"; // Make sure the path to your search icon is correct
1+
import React, { useState } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
import searchIcon from "../assets/Search.svg";
34

45
const SearchBar: React.FC = () => {
6+
const navigate = useNavigate();
7+
const [query, setQuery] = useState("");
8+
9+
const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
10+
e.preventDefault();
11+
navigate(`/search-results?query=${encodeURIComponent(query)}`);
12+
};
13+
514
return (
6-
<div className="flex-1 max-w-lg relative">
15+
<form onSubmit={handleSearch} className="flex-1 max-w-lg relative">
716
<input
817
type="search"
918
className="w-full rounded-full border border-neutral-300 pl-10 pr-4 py-3 text-neutral-700 focus:border-blue-500 focus:ring-0"
10-
placeholder="Search for services that you require..."
19+
placeholder="Type in your question and we will do the rest!"
1120
aria-label="Search"
21+
value={query}
22+
onChange={(e) => setQuery(e.target.value)}
1223
/>
1324
<button
1425
className="absolute inset-y-0 left-0 flex items-center pl-3"
15-
type="button"
26+
type="submit"
1627
>
1728
<img
1829
src={searchIcon}
1930
alt="Search icon"
2031
className="h-5 w-5 text-gray-500"
2132
/>
2233
</button>
23-
</div>
34+
</form>
2435
);
2536
};
2637

Diff for: frontend/src/pages/SearchResultsPage.tsx

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// SearchResultsPage.tsx
2+
import React, { useState, useEffect } from "react";
3+
import { useLocation } from "react-router-dom";
4+
5+
const SearchResultsPage: React.FC = () => {
6+
const location = useLocation();
7+
const query = new URLSearchParams(location.search).get("query");
8+
const [results, setResults] = useState([]);
9+
const [isLoading, setIsLoading] = useState(false);
10+
11+
useEffect(() => {
12+
const fetchResults = async () => {
13+
if (query) {
14+
setIsLoading(true);
15+
try {
16+
const response = await fetch("http://localhost:5001/recommend", {
17+
method: "POST",
18+
headers: {
19+
"Content-Type": "application/json",
20+
},
21+
body: JSON.stringify({ message: query }),
22+
});
23+
const data = await response.json();
24+
setResults(data); // Assuming the endpoint returns an array of results
25+
} catch (error) {
26+
console.error("Failed to fetch recommendations:", error);
27+
} finally {
28+
setIsLoading(false);
29+
}
30+
}
31+
};
32+
33+
fetchResults();
34+
}, [query]);
35+
36+
if (isLoading) return <div>Loading...</div>;
37+
if (!results.length) return <div>No results found.</div>;
38+
39+
return (
40+
<div className="container mx-auto px-4 py-8">
41+
{/* Render your results here */}
42+
{results.map((result, index) => (
43+
<div key={index}>{/* Render result */}</div>
44+
))}
45+
</div>
46+
);
47+
};
48+
49+
export default SearchResultsPage;

Diff for: frontend/src/pages/WorkerDetailsPage.tsx

+51-1
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,54 @@ import React, { useState, useEffect } from "react";
22
import { useParams, useNavigate } from "react-router-dom";
33
import profilePic from "../assets/User.jpeg";
44
import ReviewItem from "../components/ReviewItem";
5+
import { useAuth0 } from "@auth0/auth0-react";
6+
import ReviewModal from "../components/ReviewModal";
57

68
const WorkerDetails: React.FC = () => {
79
const { workerId } = useParams<{ workerId: string }>();
810
const navigate = useNavigate();
911
const [worker, setWorker] = useState<any>(null);
1012
const [isLoading, setIsLoading] = useState(true);
1113

14+
const { user, isAuthenticated } = useAuth0();
15+
const [showReviewForm, setShowReviewForm] = useState(false);
16+
const [isReviewModalOpen, setIsReviewModalOpen] = useState(false);
17+
const [review, setReview] = useState({
18+
rating: 0,
19+
comment: "",
20+
date: new Date().toISOString().split("T")[0], // format as 'YYYY-MM-DD'
21+
});
22+
23+
const handleReviewSubmit = async (review) => {
24+
if (isAuthenticated && user) {
25+
try {
26+
const response = await fetch("http://localhost:5001/add_review", {
27+
method: "POST",
28+
headers: {
29+
"Content-Type": "application/json",
30+
},
31+
body: JSON.stringify({
32+
userID: user.sub, // Auth0 user identifier
33+
tradespersonID: workerId,
34+
rating: review.rating,
35+
comment: review.comment,
36+
date: new Date().toISOString().split("T")[0],
37+
}),
38+
});
39+
40+
if (!response.ok) {
41+
throw new Error(`HTTP error! status: ${response.status}`);
42+
}
43+
44+
// Handle success, such as closing the form and showing a message
45+
setIsReviewModalOpen(false);
46+
// Refresh the reviews or show a success message...
47+
} catch (error) {
48+
console.error("Failed to submit review:", error);
49+
}
50+
}
51+
};
52+
1253
useEffect(() => {
1354
const fetchWorkerDetails = async () => {
1455
setIsLoading(true);
@@ -137,9 +178,18 @@ const WorkerDetails: React.FC = () => {
137178
<button className="bg-yellow-400 hover:bg-yellow-500 text-white font-bold py-2 px-4 rounded-r">
138179
Book Now
139180
</button>
140-
<button className="bg-purple-400 hover:bg-purple-500 text-white font-bold py-2 px-4 rounded-r">
181+
<button
182+
className="bg-purple-400 hover:bg-purple-500 text-white font-bold py-2 px-4 rounded-r"
183+
onClick={() => setIsReviewModalOpen(true)}
184+
>
141185
Leave a Review
142186
</button>
187+
<ReviewModal
188+
workerName={worker?.first_name + " " + worker?.last_name}
189+
isOpen={isReviewModalOpen}
190+
onSubmit={handleReviewSubmit}
191+
onCancel={() => setIsReviewModalOpen(false)}
192+
/>
143193
</div>
144194
</div>
145195
</div>

0 commit comments

Comments
 (0)