Added bookmark functionality
This commit is contained in:
28
frontend/src/hooks/useBookmark.js
Normal file
28
frontend/src/hooks/useBookmark.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { useMutation } from 'react-query';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { addToBookmarkService } from '../services/addToBookmarkService';
|
||||||
|
|
||||||
|
function useBookmark() {
|
||||||
|
|
||||||
|
const BookmarkMutation = useMutation(addToBookmarkService, {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast.success(data?.message);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.log(error);
|
||||||
|
toast.error(
|
||||||
|
error?.response?.data?.message || "An unexpected error occurred"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleBookmark = (pollId) => {
|
||||||
|
BookmarkMutation.mutate(pollId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {handleBookmark, BookmarkMutation};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useBookmark
|
||||||
@@ -22,7 +22,7 @@ const dummyBookmarks = [
|
|||||||
function Bookmark() {
|
function Bookmark() {
|
||||||
return (
|
return (
|
||||||
<div className="p-6 bg-base-200 h-screen">
|
<div className="p-6 bg-base-200 h-screen">
|
||||||
<h1 className="text-4xl font-bold text-white mb-6">Bookmarked Polls</h1>
|
<h1 className="md:text-4xl text-xl font-bold text-white mb-6">Bookmarked Polls</h1>
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="table w-full bg-base-100">
|
<table className="table w-full bg-base-100">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -37,12 +37,12 @@ function Bookmark() {
|
|||||||
{dummyBookmarks.map((bookmark, index) => (
|
{dummyBookmarks.map((bookmark, index) => (
|
||||||
<tr key={bookmark.id}>
|
<tr key={bookmark.id}>
|
||||||
<th>{index + 1}</th>
|
<th>{index + 1}</th>
|
||||||
<td className="text-white">{bookmark.title}</td>
|
<td className="text-white text-sm md:text-base">{bookmark.title}</td>
|
||||||
<td className="text-gray-400 whitespace-normal break-words max-w-xs">
|
<td className="text-gray-400 whitespace-normal break-words max-w-xs text-xs md:text-base">
|
||||||
{bookmark.description}
|
{bookmark.description}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button className="btn btn-primary">
|
<button className="btn btn-primary text-sm md:text-base">
|
||||||
View Poll
|
View Poll
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
// VotingPage.js
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Bar } from "react-chartjs-2";
|
import { Bar } from "react-chartjs-2";
|
||||||
import { Chart as ChartJS, BarElement, CategoryScale, LinearScale } from "chart.js";
|
import {
|
||||||
|
Chart as ChartJS,
|
||||||
|
BarElement,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
} from "chart.js";
|
||||||
import { useMutation, useQuery } from "react-query";
|
import { useMutation, useQuery } from "react-query";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import getPollData from "../services/getPollData";
|
import getPollData from "../services/getPollData";
|
||||||
@@ -9,15 +13,22 @@ import ErrorFallback from "../components/Errors/ErrorFallback";
|
|||||||
import createVoteService from "../services/createVoteService";
|
import createVoteService from "../services/createVoteService";
|
||||||
import { FaBookmark } from "react-icons/fa";
|
import { FaBookmark } from "react-icons/fa";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
import { makeChartDataObjFromPollData } from "../utils/util";
|
||||||
|
import useBookmark from "../hooks/useBookmark";
|
||||||
|
|
||||||
ChartJS.register(BarElement, CategoryScale, LinearScale);
|
ChartJS.register(BarElement, CategoryScale, LinearScale);
|
||||||
|
|
||||||
function VotingPage() {
|
function VotingPage() {
|
||||||
|
|
||||||
const { pollId } = useParams();
|
const { pollId } = useParams();
|
||||||
const [seletedOption, setSeletedOption] = useState(null);
|
const [seletedOption, setSeletedOption] = useState(null);
|
||||||
|
const {handleBookmark} = useBookmark()
|
||||||
|
|
||||||
const { data: poll, isLoading, isError, refetch } = useQuery(["poll", pollId], () => getPollData(pollId), {
|
const {
|
||||||
|
data: poll,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
refetch,
|
||||||
|
} = useQuery(["poll", pollId], () => getPollData(pollId), {
|
||||||
cacheTime: 10 * 100 * 60, // 10 minutes
|
cacheTime: 10 * 100 * 60, // 10 minutes
|
||||||
staleTime: 20 * 100 * 60, // 20 minutes
|
staleTime: 20 * 100 * 60, // 20 minutes
|
||||||
});
|
});
|
||||||
@@ -28,25 +39,14 @@ function VotingPage() {
|
|||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
toast.error(error?.response?.data?.message || "An unexpected error occurred");
|
toast.error(
|
||||||
|
error?.response?.data?.message || "An unexpected error occurred"
|
||||||
|
);
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
const handleOptionSelect = (id) => {
|
const handleOptionSelect = (id) => {
|
||||||
mutation.mutate({ pollId, optionId: id });
|
mutation.mutate({ pollId, optionId: id });
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const chartData = {
|
|
||||||
labels: poll?.data?.pollData?.options.map(option => option.name),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: "Votes",
|
|
||||||
data: poll?.data?.pollData?.options.map(option => option.voteCount),
|
|
||||||
backgroundColor: ["#3B82F6", "#EF4444", "#10B981", "#F59E0B"],
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -54,9 +54,11 @@ function VotingPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return <div className="h-64 w-full max-w-lg mt-12 mx-auto">
|
return (
|
||||||
|
<div className="h-64 w-full max-w-lg mt-12 mx-auto">
|
||||||
<ErrorFallback onRetry={refetch} />
|
<ErrorFallback onRetry={refetch} />
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -69,27 +71,38 @@ function VotingPage() {
|
|||||||
alt={poll?.data?.creatorData?.username}
|
alt={poll?.data?.creatorData?.username}
|
||||||
className="rounded-full h-7 md:h-10 w-7 md:w-10"
|
className="rounded-full h-7 md:h-10 w-7 md:w-10"
|
||||||
/>
|
/>
|
||||||
<h2 className="text-lg md:text-xl font-semibold">{poll?.data?.creatorData?.username || "Unknown"}</h2>
|
<h2 className="text-lg md:text-xl font-semibold">
|
||||||
|
{poll?.data?.creatorData?.username || "Unknown"}
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* BookMark Button */}
|
{/* BookMark Button */}
|
||||||
<button className="btn btn-circle btn-neutral"><FaBookmark /></button>
|
<button className="btn btn-circle btn-neutral" onClick={() => handleBookmark(pollId)}>
|
||||||
|
<FaBookmark />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Poll Title */}
|
{/* Poll Title */}
|
||||||
<h1 className="text-xl md:text-3xl font-bold text-center">{poll?.data?.pollData?.title || "Loading.."}</h1>
|
<h1 className="text-xl md:text-3xl font-bold text-center">
|
||||||
|
{poll?.data?.pollData?.title || "Loading.."}
|
||||||
|
</h1>
|
||||||
|
|
||||||
{/* Poll Description */}
|
{/* Poll Description */}
|
||||||
<p className="text-sm font-light md:text-base mb-6 text-center">{poll?.data?.pollData?.description || "Loading.."}</p>
|
<p className="text-sm font-light md:text-base mb-6 text-center">
|
||||||
|
{poll?.data?.pollData?.description || "Loading.."}
|
||||||
|
</p>
|
||||||
|
|
||||||
{/* Voting Options */}
|
{/* Voting Options */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full max-w-lg mb-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full max-w-lg mb-6">
|
||||||
{poll?.data?.pollData?.options.map(option => (
|
{poll?.data?.pollData?.options.map((option) => (
|
||||||
<div
|
<div
|
||||||
onClick={() => handleOptionSelect(option._id)}
|
onClick={() => handleOptionSelect(option._id)}
|
||||||
key={option._id}
|
key={option._id}
|
||||||
className= {`md:p-4 p-2 ${seletedOption == option._id ? "bg-blue-500" : "bg-base-100"} rounded-lg shadow-md flex items-center justify-center cursor-pointer ${seletedOption == option._id ? "outline" :"hover:bg-base-300"} transition`}
|
className={`md:p-4 p-2 ${
|
||||||
|
seletedOption == option._id ? "bg-blue-500" : "bg-base-100"
|
||||||
|
} rounded-lg shadow-md flex items-center justify-center cursor-pointer ${
|
||||||
|
seletedOption == option._id ? "outline" : "hover:bg-base-300"
|
||||||
|
} transition`}
|
||||||
>
|
>
|
||||||
<span className="text-lg">{option.name}</span>
|
<span className="text-lg">{option.name}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,7 +111,10 @@ function VotingPage() {
|
|||||||
|
|
||||||
{/* Chart Visualization */}
|
{/* Chart Visualization */}
|
||||||
<div className="w-full max-w-lg">
|
<div className="w-full max-w-lg">
|
||||||
<Bar data={chartData} options={{ responsive: true, maintainAspectRatio: false }} />
|
<Bar
|
||||||
|
data={makeChartDataObjFromPollData(poll)}
|
||||||
|
options={{ responsive: true, maintainAspectRatio: false }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
6
frontend/src/services/addToBookmarkService.js
Normal file
6
frontend/src/services/addToBookmarkService.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import axiosInstance from "../helper/axiosInstance";
|
||||||
|
|
||||||
|
export async function addToBookmarkService(pollId) {
|
||||||
|
const response = await axiosInstance.get(`/poll/bookmark/${pollId}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
@@ -6,3 +6,17 @@ export const getSelectedOption = (pollId) => {
|
|||||||
const selecetedId = localStorage.getItem(pollId);
|
const selecetedId = localStorage.getItem(pollId);
|
||||||
return selecetedId;
|
return selecetedId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const makeChartDataObjFromPollData = (poll) => {
|
||||||
|
return {
|
||||||
|
labels: poll?.data?.pollData?.options.map(option => option.name),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Votes",
|
||||||
|
data: poll?.data?.pollData?.options.map(option => option.voteCount),
|
||||||
|
backgroundColor: ["#3B82F6", "#EF4444", "#10B981", "#F59E0B"],
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user