diff --git a/.github/workflows/star-reminder.yml b/.github/workflows/star-reminder.yml
index 1902552f..ba0bc218 100644
--- a/.github/workflows/star-reminder.yml
+++ b/.github/workflows/star-reminder.yml
@@ -1,11 +1,9 @@
name: Star Reminder for Contributors
on:
- pull_request:
+ pull_request_target:
types: [opened]
- # pull_request_target: (for on every pr)
-
jobs:
star-reminder:
runs-on: ubuntu-latest
diff --git a/backend/app.js b/backend/app.js
index 0a5576f7..1433af5e 100644
--- a/backend/app.js
+++ b/backend/app.js
@@ -1,12 +1,18 @@
import express from 'express';
import { devRouter } from './routes/dev.routes.js';
import { githubRouter } from './routes/github.routes.js';
+
import { subscribersRouter } from './routes/subscribers.routes.js';
import { ideasRouter } from './routes/ideas.routes.js';
import { getSubscribers } from './controllers/subscribers.controllers.js';
import Subscribers from './models/subscribers.models.js';
+import { ideaRouter } from './routes/ideaSubmission.routes.js';
+import { winnerRouter } from './routes/winner.routes.js';
+import cors from 'cors';
const app = express();
+app.use(cors());
+
// Middleware to parse incoming JSON requests
app.use(express.json());
@@ -21,6 +27,10 @@ app.use('/devdisplay/v1/ideas', ideasRouter);
app.use('/devdisplay/v1/subscribers', getSubscribers);
+// IdeaSubmission routes
+app.use('/devdisplay/v1/idea-submissions', ideaRouter);
+app.use('/devdisplay/v1/winners', winnerRouter);
+
app.get('/', (req, res) => {
res.status(200).json({
message: 'Welcome to the DevDisplay API!',
diff --git a/backend/controllers/ideaSubmission.controllers.js b/backend/controllers/ideaSubmission.controllers.js
new file mode 100644
index 00000000..abf79008
--- /dev/null
+++ b/backend/controllers/ideaSubmission.controllers.js
@@ -0,0 +1,96 @@
+import IdeaSubmission from '../models/ideaSubmission.models.js';
+
+const parseList = (val) => {
+ if (!val) return [];
+ try {
+ const parsed = JSON.parse(val);
+ return Array.isArray(parsed) ? parsed.map(String) : [];
+ } catch (error) {
+ return String(val)
+ .split(',')
+ .map((s) => s.trim())
+ .filter(Boolean);
+ }
+};
+
+export const createIdea = async (req, res) => {
+ try {
+ const title = req.body.tittle || req.body.title;
+ const description = req.body.description || '';
+
+ const tags = parseList(req.body.tags);
+ const resources = parseList(req.body.resources);
+
+ const mediaUrls = (req.files || []).map((f) => `/uploads/${f.filename}`);
+
+ if (!title || !title.trim()) {
+ return res.status(400).json({ error: 'Title is required' });
+ }
+
+ const idea = await IdeaSubmission.create({
+ title: title.trim(),
+ description,
+ tags,
+ resources,
+ mediaUrls,
+ });
+
+ res.status(201).json({ message: 'Idea created', idea });
+ } catch (err) {
+ console.error('Create idea error:', err);
+ res.status(500).json({ error: 'Server error' });
+ }
+};
+
+export const getIdeas = async (_req, res) => {
+ try {
+ const ideas = await IdeaSubmission.find().sort({ createdAt: -1 });
+ res.json(ideas);
+ } catch (err) {
+ console.error('Fetch ideas error:', err);
+ res.status(500).json({ error: 'Server error' });
+ }
+};
+
+export const getIdeaById = async (req, res) => {
+ try {
+ const idea = await IdeaSubmission.findById(req.params.id);
+ if (!idea) return res.status(404).json({ error: 'Not found' });
+ res.json(idea);
+ } catch (err) {
+ res.status(400).json({ error: 'Invalid ID' });
+ }
+};
+
+export const voteIdea = async (req, res) => {
+ try {
+ const { id } = req.params;
+ const userId = req.ip;
+
+ const idea = await IdeaSubmission.findById(id);
+ if (!idea) {
+ return res.status(404).json({ error: 'Idea not found' });
+ }
+
+ const index = idea.voters.indexOf(userId);
+
+ if (index === -1) {
+ idea.voters.push(userId);
+ idea.votes += 1;
+ } else {
+ idea.voters.splice(index, 1);
+ idea.votes -= 1;
+ }
+
+ await idea.save();
+
+ res.json({
+ message: 'Vote toggled',
+ votes: idea.votes,
+ voters: idea.voters,
+ idea,
+ });
+ } catch (err) {
+ res.status(500).json({ error: 'Server error', details: err.message });
+ }
+};
diff --git a/backend/controllers/winner.controller.js b/backend/controllers/winner.controller.js
new file mode 100644
index 00000000..e48a1bb5
--- /dev/null
+++ b/backend/controllers/winner.controller.js
@@ -0,0 +1,34 @@
+import winnerModels from '../models/winner.models.js';
+import { selectWinnerForMonth } from '../services/winner.service.js';
+
+export const getLatestWinner = async (req, res) => {
+ try {
+ const latest = await winnerModels.findOne().sort({ createdAt: -1 }).populate('ideaId');
+
+ if (!latest) return res.json({ message: 'No winner found' });
+ res.json(latest);
+ } catch (error) {
+ console.error('Error fetching latest winner:', error);
+ res.status(500).json({ message: 'Internal server error' });
+ }
+};
+
+export const selectWinner = async (req, res) => {
+ try {
+ const now = new Date();
+ const year = Number(req.query.year ?? now.getFullYear());
+ const month = Number(req.query.month ?? now.getMonth());
+
+ const winner = await selectWinnerForMonth(year, month);
+
+ if (!winner) {
+ return res.status(404).json({ message: 'No ideas found for the specified month' });
+ }
+
+ const populated = await winner.populate('ideaId'); // β
fixed field
+ res.json({ message: 'Winner selected', winner: populated });
+ } catch (e) {
+ console.error('Error selecting winner:', e);
+ res.status(500).json({ message: 'Internal server error' });
+ }
+};
diff --git a/backend/cron/monthlyWinner.cron.js b/backend/cron/monthlyWinner.cron.js
new file mode 100644
index 00000000..b47250e2
--- /dev/null
+++ b/backend/cron/monthlyWinner.cron.js
@@ -0,0 +1,31 @@
+import cron from 'node-cron';
+import { selectWinnerForMonth } from '../services/winner.service.js';
+
+cron.schedule(
+ '59 23 28-31 * *',
+ async () => {
+ const now = new Date();
+ const tomorrow = new Date(now);
+ tomorrow.setDate(now.getDate() + 1);
+
+ if (tomorrow.getMonth() !== now.getMonth()) {
+ const year = now.getFullYear();
+ const month = now.getMonth();
+
+ console.log(`Running end-of-month winner selection for ${year}-${month + 1}`);
+
+ try {
+ const winner = await selectWinnerForMonth(year, month);
+
+ if (!winner) {
+ console.log('No eligible ideas to select.');
+ } else {
+ console.log('Winner selected:', winner._id.toString());
+ }
+ } catch (error) {
+ console.error(`Error occurred while selecting monthly winner for ${year}-${month + 1}:`, error);
+ }
+ }
+ },
+ { timezone: 'Asia/Kolkata' },
+);
diff --git a/backend/index.js b/backend/index.js
index a865553c..c61e3d84 100644
--- a/backend/index.js
+++ b/backend/index.js
@@ -4,6 +4,7 @@ import dotenv from 'dotenv';
import pingDB from './cron/test.cron.js';
import * as devCronJobs from './cron/dev.cron.js';
import * as githubCronJobs from './cron/github.cron.js';
+import * as monthlyWinnerCron from './cron/monthlyWinner.cron.js';
dotenv.config({
path: './.env',
diff --git a/backend/models/ideaSubmission.models.js b/backend/models/ideaSubmission.models.js
new file mode 100644
index 00000000..dc6a3b07
--- /dev/null
+++ b/backend/models/ideaSubmission.models.js
@@ -0,0 +1,18 @@
+import mongoose from 'mongoose';
+
+const IdeaSchema = new mongoose.Schema(
+ {
+ title: { type: String, required: true, trim: true, minlength: 2, maxlength: 140 },
+ description: { type: String, default: '' },
+ tags: { type: [String], default: [] },
+ resources: { type: [String], default: [] },
+ mediaUrls: { type: [String], default: [] },
+ votes: { type: Number, default: 0 },
+ voters: [{ type: String }],
+ },
+ { timestamps: true },
+);
+
+const IdeaSubmission = mongoose.model('IdeaSubmission', IdeaSchema);
+
+export default IdeaSubmission;
diff --git a/backend/models/winner.models.js b/backend/models/winner.models.js
new file mode 100644
index 00000000..3b239509
--- /dev/null
+++ b/backend/models/winner.models.js
@@ -0,0 +1,11 @@
+import mongoose from 'mongoose';
+
+const winnerSchema = new mongoose.Schema({
+ ideaId: { type: mongoose.Schema.Types.ObjectId, ref: 'IdeaSubmission', required: true },
+ month: { type: Number, required: true },
+ year: { type: Number, required: true },
+ createdAt: { type: Date, default: Date.now },
+});
+
+winnerSchema.index({ month: 1, year: 1 }, { unique: true });
+export default mongoose.model('Winner', winnerSchema);
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 43e88a6d..d2bdbd93 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -11,10 +11,12 @@
"dependencies": {
"axios": "^1.8.2",
"cheerio": "^1.0.0",
+ "cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"mongoose": "^8.12.1",
"mongoose-aggregate-paginate-v2": "^1.1.4",
+ "multer": "^2.0.2",
"node-cron": "^3.0.3"
},
"devDependencies": {
@@ -75,6 +77,11 @@
"node": ">= 8"
}
},
+ "node_modules/append-field": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
+ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
+ },
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@@ -193,6 +200,22 @@
"node": ">=16.20.1"
}
},
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -317,6 +340,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/concat-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
+ "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
+ "engines": [
+ "node >= 6.0"
+ ],
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.0.2",
+ "typedarray": "^0.0.6"
+ }
+ },
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -353,6 +390,18 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"license": "MIT"
},
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
@@ -1076,6 +1125,25 @@
"node": "*"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
"node_modules/mongodb": {
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.14.2.tgz",
@@ -1219,6 +1287,23 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
+ "node_modules/multer": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz",
+ "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==",
+ "dependencies": {
+ "append-field": "^1.0.0",
+ "busboy": "^1.6.0",
+ "concat-stream": "^2.0.0",
+ "mkdirp": "^0.5.6",
+ "object-assign": "^4.1.1",
+ "type-is": "^1.6.18",
+ "xtend": "^4.0.2"
+ },
+ "engines": {
+ "node": ">= 10.16.0"
+ }
+ },
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -1232,7 +1317,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
"integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
- "license": "ISC",
"dependencies": {
"uuid": "8.3.2"
},
@@ -1316,6 +1400,14 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -1491,6 +1583,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -1712,6 +1817,22 @@
"node": ">= 0.8"
}
},
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -1782,6 +1903,11 @@
"node": ">= 0.6"
}
},
+ "node_modules/typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
+ },
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
@@ -1807,6 +1933,11 @@
"node": ">= 0.8"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
+ },
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -1876,6 +2007,14 @@
"engines": {
"node": ">=18"
}
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "engines": {
+ "node": ">=0.4"
+ }
}
}
}
diff --git a/backend/package.json b/backend/package.json
index 9f5c9fb0..5e560f90 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -16,10 +16,12 @@
"dependencies": {
"axios": "^1.8.2",
"cheerio": "^1.0.0",
+ "cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"mongoose": "^8.12.1",
"mongoose-aggregate-paginate-v2": "^1.1.4",
+ "multer": "^2.0.2",
"node-cron": "^3.0.3"
},
"devDependencies": {
diff --git a/backend/routes/ideaSubmission.routes.js b/backend/routes/ideaSubmission.routes.js
new file mode 100644
index 00000000..5e7b9a3a
--- /dev/null
+++ b/backend/routes/ideaSubmission.routes.js
@@ -0,0 +1,30 @@
+import express from 'express';
+import multer from 'multer';
+import path from 'path';
+import { createIdea, getIdeaById, getIdeas, voteIdea } from '../controllers/ideaSubmission.controllers.js';
+
+const router = express.Router();
+
+const uploadDir = path.join(process.cwd(), 'uploads');
+const storage = multer.diskStorage({
+ destination: (req, file, cb) => cb(null, uploadDir),
+ filename: (req, file, cb) => {
+ const ext = path.extname(file.originalname);
+ const base = path.basename(file.originalname, ext);
+ cb(null, `${Date.now()}-${base}${ext}`);
+ },
+});
+const fileFilter = (_req, file, cb) => {
+ const allowed = ['image/png', 'image/jpeg', 'image/jpg', 'image/webp', 'image/gif'];
+ if (allowed.includes(file.mimetype)) cb(null, true);
+ else cb(new Error('Only image files are allowed'));
+};
+const upload = multer({ storage, fileFilter, limits: { fileSize: 5 * 1024 * 1024 } });
+
+// Routes
+router.post('/', upload.array('media', 10), createIdea);
+router.get('/', getIdeas);
+router.get('/:id', getIdeaById);
+router.post('/:id/vote', voteIdea);
+
+export { router as ideaRouter };
diff --git a/backend/routes/winner.routes.js b/backend/routes/winner.routes.js
new file mode 100644
index 00000000..54629de4
--- /dev/null
+++ b/backend/routes/winner.routes.js
@@ -0,0 +1,10 @@
+import express from 'express';
+
+import { getLatestWinner, selectWinner } from '../controllers/winner.controller.js';
+
+const router = express.Router();
+
+router.get('/latest', getLatestWinner);
+router.post('/select', selectWinner);
+
+export { router as winnerRouter };
diff --git a/backend/services/winner.service.js b/backend/services/winner.service.js
new file mode 100644
index 00000000..a4cb6b1f
--- /dev/null
+++ b/backend/services/winner.service.js
@@ -0,0 +1,24 @@
+import winnerModels from '../models/winner.models.js';
+import IdeaSubmission from '../models/ideaSubmission.models.js';
+
+export async function selectWinnerForMonth(year, month) {
+ const start = new Date(year, month, 1, 0, 0, 0, 0);
+ const end = new Date(year, month + 1, 1, 0, 0, 0, 0);
+
+ const existing = await winnerModels.findOne({ month, year });
+ if (existing) return existing;
+
+ const topIdea = await IdeaSubmission.find({ createdAt: { $gte: start, $lt: end } })
+ .sort({ votes: -1 })
+ .limit(1);
+
+ if (!topIdea.length) return null;
+
+ const winner = await winnerModels.create({
+ ideaId: topIdea[0]._id,
+ month,
+ year,
+ });
+
+ return winner;
+}
diff --git a/src/App.js b/src/App.js
index 0b18e10f..c5b4e439 100644
--- a/src/App.js
+++ b/src/App.js
@@ -56,7 +56,7 @@ import DevShare from './Page/ResoucesHub/DevShare.jsx';
import PageNotFound from './Page/PageNotFound.jsx';
import ProfilePage from './components/Profile/ProfilePage';
import { ResumeProvider } from './components/ResumeBuilder/context/ResumeContext.jsx';
-
+import Collaboration from './Page/Collaboration.jsx';
function App() {
React.useEffect(() => {
document.documentElement.classList.add('dark');
@@ -116,6 +116,7 @@ function App() {
{idea.description}
+45% Completed
+Submission Period: {submissionStatus.submissionPeriod}
- )} -Be the first to submit an innovative idea this month!
+No roles have been assigned yet.
} + +