Added websocket
This commit is contained in:
@@ -5,3 +5,4 @@ export const PORT = Number(process.env.PORT);
|
|||||||
export const DB_URL = process.env.DB_CONNECTION;
|
export const DB_URL = process.env.DB_CONNECTION;
|
||||||
export const SALT = Number(process.env.SALT_ROUNDS);
|
export const SALT = Number(process.env.SALT_ROUNDS);
|
||||||
export const JWT_PRIVATE = process.env.JWT_PRIVATE;
|
export const JWT_PRIVATE = process.env.JWT_PRIVATE;
|
||||||
|
export const CLIENT_URL = process.env.CLIENT_URL;
|
||||||
25
backend/src/controllers/vote.controller.js
Normal file
25
backend/src/controllers/vote.controller.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { voteMessageTestService } from "../services/vote.service.js";
|
||||||
|
|
||||||
|
export async function voteTestController(req, res) {
|
||||||
|
try{
|
||||||
|
const message = voteMessageTestService();
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch(err) {
|
||||||
|
console.log(err);
|
||||||
|
if (err.statusCode) {
|
||||||
|
res.status(err.statusCode).json({
|
||||||
|
success: false,
|
||||||
|
message: err.message,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: err.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,14 +2,15 @@ import express from 'express'
|
|||||||
import cors from 'cors'
|
import cors from 'cors'
|
||||||
import { createServer } from 'http'
|
import { createServer } from 'http'
|
||||||
import { Server } from 'socket.io'
|
import { Server } from 'socket.io'
|
||||||
import { PORT } from './config/veriables.js';
|
import { CLIENT_URL, PORT } from './config/veriables.js';
|
||||||
import { connectDB } from './config/dbConfig.js';
|
import { connectDB } from './config/dbConfig.js';
|
||||||
import userRouter from './routes/v1/user.route.js';
|
import userRouter from './routes/v1/user.route.js';
|
||||||
import swaggerDocs from '../swagger.js';
|
import swaggerDocs from '../swagger.js';
|
||||||
import swaggerUi from 'swagger-ui-express';
|
import swaggerUi from 'swagger-ui-express';
|
||||||
import cookieParser from "cookie-parser";
|
import cookieParser from "cookie-parser";
|
||||||
import pollRouter from './routes/v1/poll.route.js';
|
import pollRouter from './routes/v1/poll.route.js';
|
||||||
|
import { handlePollSocket } from './socket/poll.socket.js';
|
||||||
|
import voteRouter from './routes/v1/vote.route.js';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const httpServer = createServer(app);
|
const httpServer = createServer(app);
|
||||||
@@ -18,13 +19,17 @@ app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs));
|
|||||||
|
|
||||||
const io = new Server(httpServer, {
|
const io = new Server(httpServer, {
|
||||||
cors: {
|
cors: {
|
||||||
origin: "*"
|
origin: CLIENT_URL,
|
||||||
|
methods: ["GET", "POST", "PUT", "DELETE"]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
handlePollSocket(io);
|
||||||
|
|
||||||
|
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.use(cors({
|
app.use(cors({
|
||||||
origin: "http://localhost:5173",
|
origin: CLIENT_URL,
|
||||||
credentials: true
|
credentials: true
|
||||||
}))
|
}))
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
@@ -34,6 +39,7 @@ app.get("/ping", (_req, res) => {
|
|||||||
|
|
||||||
app.use("/api/v1/poll", pollRouter);
|
app.use("/api/v1/poll", pollRouter);
|
||||||
app.use("/api/v1/user", userRouter);
|
app.use("/api/v1/user", userRouter);
|
||||||
|
app.use("/api/v1/vote", voteRouter);
|
||||||
|
|
||||||
await connectDB();
|
await connectDB();
|
||||||
httpServer.listen(PORT, () => {
|
httpServer.listen(PORT, () => {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export async function updatePollVoteCount(pollId, optionId) {
|
|||||||
export async function findPolls(page, limit) {
|
export async function findPolls(page, limit) {
|
||||||
try{
|
try{
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
const polls = await PollModel.find().skip(skip).limit(limit).populate("creatorId");
|
const polls = await PollModel.find().sort({createdAt : -1}).skip(skip).limit(limit).populate("creatorId");
|
||||||
return polls;
|
return polls;
|
||||||
}
|
}
|
||||||
catch(err){
|
catch(err){
|
||||||
|
|||||||
19
backend/src/routes/v1/vote.route.js
Normal file
19
backend/src/routes/v1/vote.route.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import express from "express";
|
||||||
|
import { verifyToken } from "../../middlwares/verifyToken.js";
|
||||||
|
import { voteTestController } from "../../controllers/vote.controller.js";
|
||||||
|
const voteRouter = express.Router();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /vote/test:
|
||||||
|
* get:
|
||||||
|
* summary: Test route for vote
|
||||||
|
* tags: [Vote]
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Success
|
||||||
|
*/
|
||||||
|
voteRouter.get("/test", voteTestController)
|
||||||
|
// voteRouter.get("/voted/:pollId", verifyToken, getVotedDataController);
|
||||||
|
|
||||||
|
export default voteRouter;
|
||||||
8
backend/src/services/vote.service.js
Normal file
8
backend/src/services/vote.service.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export function voteMessageTestService(){
|
||||||
|
try{
|
||||||
|
return "Vote route is working✔️";
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
backend/src/socket/poll.socket.js
Normal file
34
backend/src/socket/poll.socket.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { getPollDataService } from "../services/poll.service.js";
|
||||||
|
|
||||||
|
export const handlePollSocket = (io) => {
|
||||||
|
io.on("connection", (socket) => {
|
||||||
|
console.log(`User connected: ${socket.id}`);
|
||||||
|
|
||||||
|
socket.on("joinPoll", (pollId) => {
|
||||||
|
socket.join(pollId);
|
||||||
|
console.log(`User ${socket.id} joined poll room: ${pollId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("disconnect", () => {
|
||||||
|
console.log(`User disconnected: ${socket.id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("vote", async (data) => {
|
||||||
|
if (data.success) {
|
||||||
|
console.log("Vote received:", data);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pollData = await getPollDataService(data.pollId);
|
||||||
|
|
||||||
|
io.to(data.pollId).emit("pollDataUpdated", { data: pollData });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching poll data:", error);
|
||||||
|
socket.emit("error", { message: "Failed to fetch poll data" });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Vote failed:", data);
|
||||||
|
socket.emit("error", { message: "Vote was unsuccessful" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
88
frontend/package-lock.json
generated
88
frontend/package-lock.json
generated
@@ -17,6 +17,7 @@
|
|||||||
"react-query": "^3.39.3",
|
"react-query": "^3.39.3",
|
||||||
"react-router-dom": "^6.27.0",
|
"react-router-dom": "^6.27.0",
|
||||||
"react-toastify": "^10.0.6",
|
"react-toastify": "^10.0.6",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
"zustand": "^5.0.1"
|
"zustand": "^5.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -1338,6 +1339,12 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@socket.io/component-emitter": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
@@ -2183,7 +2190,6 @@
|
|||||||
"version": "4.3.7",
|
"version": "4.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@@ -2303,6 +2309,28 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/engine.io-client": {
|
||||||
|
"version": "6.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz",
|
||||||
|
"integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.1",
|
||||||
|
"engine.io-parser": "~5.2.1",
|
||||||
|
"ws": "~8.17.1",
|
||||||
|
"xmlhttprequest-ssl": "~2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io-parser": {
|
||||||
|
"version": "5.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||||
|
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-abstract": {
|
"node_modules/es-abstract": {
|
||||||
"version": "1.23.3",
|
"version": "1.23.3",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz",
|
||||||
@@ -4024,7 +4052,6 @@
|
|||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/mz": {
|
"node_modules/mz": {
|
||||||
@@ -5141,6 +5168,34 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/socket.io-client": {
|
||||||
|
"version": "4.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
|
||||||
|
"integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.2",
|
||||||
|
"engine.io-client": "~6.6.1",
|
||||||
|
"socket.io-parser": "~4.2.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-parser": {
|
||||||
|
"version": "4.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||||
|
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@@ -5947,6 +6002,35 @@
|
|||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||||
|
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xmlhttprequest-ssl": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"react-query": "^3.39.3",
|
"react-query": "^3.39.3",
|
||||||
"react-router-dom": "^6.27.0",
|
"react-router-dom": "^6.27.0",
|
||||||
"react-toastify": "^10.0.6",
|
"react-toastify": "^10.0.6",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
"zustand": "^5.0.1"
|
"zustand": "^5.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
const ErrorFallback = ({ onRetry }) => {
|
const ErrorFallback = ({ onRetry }) => {
|
||||||
return (
|
return (
|
||||||
@@ -14,6 +15,9 @@ const ErrorFallback = ({ onRetry }) => {
|
|||||||
>
|
>
|
||||||
Retry
|
Retry
|
||||||
</button>
|
</button>
|
||||||
|
<Link to="/" className="btn btn-ghost w-full">
|
||||||
|
Go Home
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useQuery, useQueryClient } from "react-query";
|
|||||||
import ErrorFallback from "../components/Errors/ErrorFallback";
|
import ErrorFallback from "../components/Errors/ErrorFallback";
|
||||||
import useBookmark from "../hooks/useBookmark";
|
import useBookmark from "../hooks/useBookmark";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { formatDataByDate } from "../utils/util";
|
||||||
|
|
||||||
function Bookmark() {
|
function Bookmark() {
|
||||||
const { handleBookmark } = useBookmark();
|
const { handleBookmark } = useBookmark();
|
||||||
@@ -34,6 +35,7 @@ function Bookmark() {
|
|||||||
});
|
});
|
||||||
await handleBookmark(bookmarkId);
|
await handleBookmark(bookmarkId);
|
||||||
};
|
};
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 bg-base-200 h-screen">
|
<div className="p-6 bg-base-200 h-screen">
|
||||||
@@ -58,7 +60,7 @@ function Bookmark() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{data?.data?.map((bookmark, index) => (
|
{formatDataByDate(data.data).map((bookmark, index) => (
|
||||||
<tr key={bookmark._id}>
|
<tr key={bookmark._id}>
|
||||||
<th>{index + 1}</th>
|
<th>{index + 1}</th>
|
||||||
<td className="text-white text-sm md:text-base">
|
<td className="text-white text-sm md:text-base">
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import { FaPlus, FaTrashAlt } from "react-icons/fa";
|
|||||||
import { useMutation } from "react-query";
|
import { useMutation } from "react-query";
|
||||||
import createPollService from "../services/createPollService";
|
import createPollService from "../services/createPollService";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
function CreatePollForm() {
|
function CreatePollForm() {
|
||||||
const [title, setTitle] = useState("");
|
const [title, setTitle] = useState("");
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
const [options, setOptions] = useState([]);
|
const [options, setOptions] = useState([]);
|
||||||
const [optionInput, setOptionInput] = useState("");
|
const [optionInput, setOptionInput] = useState("");
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleAddOption = () => {
|
const handleAddOption = () => {
|
||||||
if (optionInput.trim() == "") {
|
if (optionInput.trim() == "") {
|
||||||
@@ -31,6 +33,7 @@ function CreatePollForm() {
|
|||||||
const message = data?.message || "Poll created successfully";
|
const message = data?.message || "Poll created successfully";
|
||||||
toast.success(message);
|
toast.success(message);
|
||||||
handleClearPoll();
|
handleClearPoll();
|
||||||
|
navigate(`/view/${data?.data?._id}`);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import useLogout from "../hooks/useLogout";
|
|||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
import getUserPollData from "../services/getUserPollData";
|
import getUserPollData from "../services/getUserPollData";
|
||||||
import ErrorFallback from "../components/Errors/ErrorFallback";
|
import ErrorFallback from "../components/Errors/ErrorFallback";
|
||||||
|
import { formatDataByDate } from "../utils/util";
|
||||||
|
|
||||||
function Dashboard() {
|
function Dashboard() {
|
||||||
const navigator = useNavigate();
|
const navigator = useNavigate();
|
||||||
@@ -23,7 +24,6 @@ function Dashboard() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
const pollData = [
|
const pollData = [
|
||||||
{
|
{
|
||||||
@@ -108,7 +108,7 @@ function Dashboard() {
|
|||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{data.map((poll, index) => (
|
{formatDataByDate(data)?.map((poll, index) => (
|
||||||
<PollTableRow key={poll._id} refetch={refetch} poll={poll} index={index} /> // Replace with actual path
|
<PollTableRow key={poll._id} refetch={refetch} poll={poll} index={index} /> // Replace with actual path
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1,38 +1,59 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from "react";
|
||||||
import { useQuery } from 'react-query'
|
import { useQuery } from "react-query";
|
||||||
import getPollsService from '../services/getPollsService'
|
import getPollsService from "../services/getPollsService";
|
||||||
import PollCard from '../components/PollCard/PollCard'
|
import PollCard from "../components/PollCard/PollCard";
|
||||||
import ErrorFallback from '../components/Errors/ErrorFallback'
|
import ErrorFallback from "../components/Errors/ErrorFallback";
|
||||||
|
|
||||||
function Polls() {
|
function Polls() {
|
||||||
|
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [limit, setLimit] = useState(6);
|
const [limit, setLimit] = useState(6);
|
||||||
|
|
||||||
const {data, isLoading, isError, isSuccess, refetch} = useQuery(["polls", page, limit], () => getPollsService(page, limit), {
|
const { data, isLoading, isError, isSuccess, refetch } = useQuery(
|
||||||
cacheTime: 1000 * 60 * 5, // 5 minutes
|
["polls", page, limit],
|
||||||
staleTime: 1000 * 60 * 10, // 10 minutes
|
() => getPollsService(page, limit),
|
||||||
})
|
{
|
||||||
|
cacheTime: 1000 * 60 * 5,
|
||||||
|
staleTime: 1000 * 60 * 10,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto p-4 bg-base-200">
|
<div className="container mx-auto p-4 bg-base-200">
|
||||||
<h1 className="text-2xl font-bold text-center mb-6">Polls</h1>
|
<h1 className="text-2xl font-bold text-center mb-6">Polls</h1>
|
||||||
{isSuccess && <div className="flex flex-wrap justify-center gap-6">
|
{isSuccess && (
|
||||||
{data?.data?.polls?.map((poll) => (
|
<div className="flex flex-wrap justify-center gap-6">
|
||||||
<PollCard key={poll._id} poll={poll} />
|
{data?.data?.polls?.map((poll) => (
|
||||||
))}
|
<PollCard key={poll._id} poll={poll} />
|
||||||
</div>}
|
))}
|
||||||
{isLoading && <div className='flex flex-wrap justify-center gap-6 skeleton min-h-40'></div>}
|
</div>
|
||||||
{isError && <div className='flex justify-center gap-6'>
|
)}
|
||||||
<ErrorFallback onRetry={() => refetch()} />
|
{isLoading && (
|
||||||
</div>}
|
<div className="flex flex-wrap justify-center gap-6 skeleton min-h-40"></div>
|
||||||
|
)}
|
||||||
|
{isError && (
|
||||||
|
<div className="flex justify-center gap-6">
|
||||||
|
<ErrorFallback onRetry={() => refetch()} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className='flex justify-center gap-6 mt-6'>
|
<div className="flex justify-center gap-6 mt-6">
|
||||||
<button className='btn btn-primary btn-circle' disabled={page <= 1} onClick={() => setPage(page - 1)}>Prev</button>
|
<button
|
||||||
<button className='btn btn-primary btn-circle' disabled={data?.data?.totalPages === page} onClick={() => setPage(page + 1)}>Next</button>
|
className="btn btn-primary btn-circle"
|
||||||
|
disabled={page <= 1}
|
||||||
|
onClick={() => setPage(page - 1)}
|
||||||
|
>
|
||||||
|
Prev
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary btn-circle"
|
||||||
|
disabled={data?.data?.totalPages === page}
|
||||||
|
onClick={() => setPage(page + 1)}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Polls
|
export default Polls;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Bar } from "react-chartjs-2";
|
import { Bar } from "react-chartjs-2";
|
||||||
import {
|
import {
|
||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
@@ -15,30 +15,73 @@ import { FaBookmark } from "react-icons/fa";
|
|||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { makeChartDataObjFromPollData } from "../utils/util";
|
import { makeChartDataObjFromPollData } from "../utils/util";
|
||||||
import useBookmark from "../hooks/useBookmark";
|
import useBookmark from "../hooks/useBookmark";
|
||||||
|
import { io } from "socket.io-client";
|
||||||
|
|
||||||
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 [selectedOption, setSelectedOption] = useState(null);
|
||||||
const {handleBookmark} = useBookmark()
|
const { handleBookmark } = useBookmark();
|
||||||
|
const [poll, setPoll] = useState(null);
|
||||||
|
const [socket, setSocket] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const s = io("http://localhost:3000");
|
||||||
|
setSocket(s);
|
||||||
|
|
||||||
|
s.on("connect", () => {
|
||||||
|
console.log("Connected to the server");
|
||||||
|
s.emit("joinPoll", pollId);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
s.disconnect();
|
||||||
|
};
|
||||||
|
}, [pollId]);
|
||||||
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: poll,
|
data,
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
refetch,
|
refetch,
|
||||||
} = useQuery(["poll", pollId], () => getPollData(pollId), {
|
} = useQuery(["poll", pollId], () => getPollData(pollId), {
|
||||||
cacheTime: 10 * 100 * 60, // 10 minutes
|
cacheTime: 10 * 60 * 1000, // 10 minutes
|
||||||
staleTime: 20 * 100 * 60, // 20 minutes
|
staleTime: 20 * 60 * 1000, // 20 minutes
|
||||||
|
onSuccess: (data) => {
|
||||||
|
setPoll(data);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (socket) {
|
||||||
|
socket.on("pollDataUpdated", (data) => {
|
||||||
|
console.log("Received updated poll data:", data);
|
||||||
|
setPoll(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("error", (error) => {
|
||||||
|
console.error("Socket error:", error.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.off("pollDataUpdated");
|
||||||
|
socket.off("error");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [socket]);
|
||||||
|
|
||||||
const mutation = useMutation(createVoteService, {
|
const mutation = useMutation(createVoteService, {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
toast.success("Vote given successfully");
|
toast.success("Vote submitted successfully");
|
||||||
|
if (socket) {
|
||||||
|
socket.emit("vote", { pollId, success: data?.success });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
toast.error(
|
toast.error(
|
||||||
error?.response?.data?.message || "An unexpected error occurred"
|
error?.response?.data?.message || "An unexpected error occurred"
|
||||||
);
|
);
|
||||||
@@ -46,6 +89,7 @@ function VotingPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleOptionSelect = (id) => {
|
const handleOptionSelect = (id) => {
|
||||||
|
setSelectedOption(id);
|
||||||
mutation.mutate({ pollId, optionId: id });
|
mutation.mutate({ pollId, optionId: id });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -61,6 +105,9 @@ function VotingPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chartData = makeChartDataObjFromPollData(poll);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-base-200 min-h-screen p-6 text-white flex flex-col items-center">
|
<div className="bg-base-200 min-h-screen p-6 text-white flex flex-col items-center">
|
||||||
<div className="w-full flex justify-between max-w-lg">
|
<div className="w-full flex justify-between max-w-lg">
|
||||||
@@ -99,9 +146,9 @@ function VotingPage() {
|
|||||||
onClick={() => handleOptionSelect(option._id)}
|
onClick={() => handleOptionSelect(option._id)}
|
||||||
key={option._id}
|
key={option._id}
|
||||||
className={`md:p-4 p-2 ${
|
className={`md:p-4 p-2 ${
|
||||||
seletedOption == option._id ? "bg-blue-500" : "bg-base-100"
|
selectedOption == option._id ? "bg-blue-500" : "bg-base-100"
|
||||||
} rounded-lg shadow-md flex items-center justify-center cursor-pointer ${
|
} rounded-lg shadow-md flex items-center justify-center cursor-pointer ${
|
||||||
seletedOption == option._id ? "outline" : "hover:bg-base-300"
|
selectedOption == option._id ? "outline" : "hover:bg-base-300"
|
||||||
} transition`}
|
} transition`}
|
||||||
>
|
>
|
||||||
<span className="text-lg">{option.name}</span>
|
<span className="text-lg">{option.name}</span>
|
||||||
|
|||||||
@@ -20,3 +20,8 @@ export const makeChartDataObjFromPollData = (poll) => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const formatDataByDate = (data) => {
|
||||||
|
const fromatedData = data.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||||
|
return fromatedData;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user