Skip to content

Commit

Permalink
Added feedback feature in style share
Browse files Browse the repository at this point in the history
  • Loading branch information
MeetDOD committed Jul 28, 2024
1 parent fbc1d95 commit 2809d5c
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 3 deletions.
10 changes: 10 additions & 0 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ model User {
linkedin String?
portfolio String?
avatar String?
feedbacks Feedback[] @relation("userFeedbacks")
}

model Feedback {
id String @id @default(auto()) @map("_id") @db.ObjectId
rating Int
comment String
userId String @db.ObjectId
user User @relation("userFeedbacks", fields: [userId], references: [id])
createdAt DateTime @default(now())
}

model Follow {
Expand Down
27 changes: 27 additions & 0 deletions backend/src/routes/user/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,4 +639,31 @@ export const checkingBlockOrUnblock = async (req: UserAuthRequest, res: Response
console.error("Error checking blocked status:", error);
res.status(500).json({ message: "Internal Server Error" });
}
};

export const createFeedback = async (req: UserAuthRequest, res: Response) => {
try {
const { rating, comment } = req.body;
const userId = req.userId;

if (!rating || !comment || !userId) {
return res.status(400).json({ error: 'Rating and comment are required.' });
}

const feedback = await prisma.feedback.create({
data: {
rating,
comment,
userId,
},
});

res.status(201).json({
message: 'Feedback created successfully.',
feedback,
});
} catch (error) {
console.error('Error creating feedback:', error);
res.status(500).json({ error: 'An unexpected error occurred!' });
}
};
5 changes: 4 additions & 1 deletion backend/src/routes/user/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
userSigninController,
userSignupController,
verifyOtpController,
userProfileUpdate
userProfileUpdate,
createFeedback
} from "./controller";
import authMiddleware from "../../middleware/auth";

Expand Down Expand Up @@ -41,4 +42,6 @@ userRouter.get("/:id/follow-status", authMiddleware, checkFollowStatusController

userRouter.get('/checkBlockedOrUnblock',authMiddleware,checkingBlockOrUnblock);

userRouter.post('/feedback', authMiddleware, createFeedback);

export default userRouter;
105 changes: 105 additions & 0 deletions frontend/src/components/FeedbackForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { IUser } from "../types.ts";
import { useState } from "react";
import axios, { AxiosError } from "axios";
import toast from "react-hot-toast";
import { Modal } from 'react-responsive-modal';
import 'react-responsive-modal/styles.css';
import "../styles/Model.css";
import { tokenState } from "../store/atoms/auth.ts";
import { useRecoilValue } from "recoil";
import { MdOutlineStarOutline, MdOutlineStar } from "react-icons/md";

interface FeedbackFormProps {
user: IUser | null,
dismiss: () => void,
open: boolean,
}

export function FeedbackForm({ user, dismiss, open }: FeedbackFormProps) {
const [rating, setRating] = useState(0);
const [comment, setComment] = useState("");
const token = useRecoilValue(tokenState);

const handleRatingChange = (rate: number) => {
setRating(rate);
};

const handleSubmit = async () => {
if (!user) {
toast.error("You must be logged in to submit feedback");
return;
}

try {
const response = await axios.post("/api/v1/user/feedback", {
rating,
comment,
userId: user.id,
}, {
headers: {
Authorization: `Bearer ${token}`
}
});

if (response.status === 201) {
toast.success("Thank you for your feedback");
dismiss();
}
} catch (error) {
if (error instanceof AxiosError) {
toast.error(error.response?.data?.error || "Failed to submit feedback");
} else {
toast.error("Failed to submit feedback");
}
}
};

return (
<Modal open={open} onClose={dismiss} center classNames={{ modal: 'customModal', overlay: 'customOverlay' }}>
<div className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">What you think about style share ?</h3>
<div className="flex flex-col space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700">Rating</label>
<div className="flex space-x-2 mt-2">
{[1, 2, 3, 4, 5].map((star) => (
<button
key={star}
className={`text-2xl ${rating >= star ? 'text-yellow-400' : 'text-gray-300'}`}
onClick={() => handleRatingChange(star)}
>
{rating >= star ? <MdOutlineStar /> : <MdOutlineStarOutline />}
</button>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Feedback</label>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
className="w-full h-32 rounded-md p-2 border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
rows={4}
/>
</div>
<div className="flex justify-end space-x-2">
<button
type="button"
onClick={dismiss}
className="inline-flex justify-center rounded-md border border-gray-300 bg-gray-50 px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Cancel
</button>
<button
type="button"
onClick={handleSubmit}
className="inline-flex justify-center px-5 rounded-md border border-transparent bg-sky-500 py-2 text-sm font-medium text-white shadow-sm hover:bg-sky-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Submit
</button>
</div>
</div>
</div>
</Modal>
);
}
17 changes: 15 additions & 2 deletions frontend/src/pages/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { FaSquareXTwitter } from "react-icons/fa6";
import { FaGithubSquare } from "react-icons/fa";
import { FaLinkedin } from "react-icons/fa";
import { FaSquareParking } from "react-icons/fa6";
import { FeedbackForm } from "../components/FeedbackForm.tsx";
const Profile = () => {
const [posts, setPosts] = useState<IPost[]>([]);
const [user, setUser] = useState<IUser | null>(null);
Expand All @@ -26,6 +27,7 @@ const Profile = () => {
const token = useRecoilValue(tokenState);
const currentUser = useRecoilValue(userState);
const [clickUpdate, setClickUpdate] = useState(false);
const [feedbackUpdate, setfeedbackUpdate] = useState(false);

useEffect(() => {
const fetchUser = async () => {
Expand Down Expand Up @@ -125,11 +127,18 @@ const Profile = () => {
{user?.linkedin && <a href={user.linkedin} target="_blank" rel="noopener noreferrer" className="hover:-translate-y-1 transition-transform duration-300"><FaLinkedin size={30} /></a>}
{user?.portfolio && <a href={user.portfolio} target="_blank" rel="noopener noreferrer" className="hover:-translate-y-1 transition-transform duration-300"><FaSquareParking size={30} /></a>}
</div>
<div className="flex flex-col items-center mb-2">
<button type="button" onClick={() => { setClickUpdate(true) }} className="bg-red-500 py-2 px-5 text-white font-semibold rounded-md text-sm hover:bg-red-600">
<div className="flex flex-row justify-center">
<div className="mb-2">
<button type="button" onClick={() => { setClickUpdate(true) }} className="bg-red-500 py-2 mx-1 px-7 text-white font-semibold rounded-md text-sm hover:bg-red-600">
Edit
</button>
</div>
<div className="mb-2">
<button type="button" onClick={() => { setfeedbackUpdate(true) }} className="bg-yellow-500 py-2 px-5 mx-1 text-white font-semibold rounded-md text-sm hover:bg-yellow-600">
Rate Us
</button>
</div>
</div>
</div>
{
!user?.verified && (
Expand Down Expand Up @@ -161,6 +170,10 @@ const Profile = () => {
clickUpdate &&
<ProfileForm user={user} dismiss={() => { setClickUpdate(false) }} open={clickUpdate} />
}
{
feedbackUpdate &&
<FeedbackForm user={user} dismiss={() => { setfeedbackUpdate(false) }} open={feedbackUpdate} />
}
<div className="mt-8 w-full">
<h4 className="font-semibold">Posts ( {user?.posts.length} )</h4>
<div className="mt-6 mb-8 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 w-full">
Expand Down

0 comments on commit 2809d5c

Please sign in to comment.