From 7e5fab18db73385675357920b4696a417ef64af5 Mon Sep 17 00:00:00 2001 From: Aaditya Hande Date: Tue, 23 Sep 2025 23:12:17 +0530 Subject: [PATCH 1/3] Add Aaditya Hande's profile information in JSON --- public/data/AadityaHande.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 public/data/AadityaHande.json diff --git a/public/data/AadityaHande.json b/public/data/AadityaHande.json new file mode 100644 index 00000000..0d9ce00f --- /dev/null +++ b/public/data/AadityaHande.json @@ -0,0 +1,22 @@ +{ + "name": "Aaditya Hande", + "location": "Pune", + "bio": "Driven software developer with expertise in network security and AI tools, passionate about building scalable solutions and mentoring new talent. Loves competitive tech challenges and automotive innovation", + "avatar": "https://github.com/AadityaHande.png", + "portfolio": "https://aadityahande.github.io/portfolio", + "skills": ["Next.js", "Node.js with Express", "React.js", "AI/ML"], + "social": { + "GitHub": "https://github.com/AadityaHande", + "LinkedIn": "https://www.linkedin.com/in/aadityahande", + "Email": "aadityahande27@gmail.com", + "Discord": "https://discord.com/users/aadityahande", + "LeetCode": "https://leetcode.com/aadityahande", + "Codeforces": "https://codeforces.com/profile/aadityahande", + "HackerRank": "https://www.hackerrank.com/aadityahande", + "StackOverflow": "https://stackoverflow.com/users/aadityahande, + "YouTube": "https://www.youtube.com/c/aadityahande", + "DevTo": "https://dev.to/aadityahande", + "Medium": "https://medium.com/@aadityahande", + "Unstop": "https://unstop.com/@aadityahande" + } +} From 923ac1a72065f7876b94d308ffec3438aa3db7b3 Mon Sep 17 00:00:00 2001 From: Aaditya Hande Date: Tue, 23 Sep 2025 23:14:03 +0530 Subject: [PATCH 2/3] Add AadityaHande.json to ProfilesList --- src/ProfilesList.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ProfilesList.json b/src/ProfilesList.json index 77ff0bd1..7330db50 100644 --- a/src/ProfilesList.json +++ b/src/ProfilesList.json @@ -569,5 +569,6 @@ "stutitiwari23.json", "thirumaleshp.json", "WonderSTK.json", - "Khushi.json" + "Khushi.json", + "AadityaHande.json" ] From ab97b84d39052e0eb7fdfd63c632f57c034aec94 Mon Sep 17 00:00:00 2001 From: AadityaHande Date: Tue, 23 Sep 2025 23:41:09 +0530 Subject: [PATCH 3/3] feat: Add comprehensive Idea Submission & Voting System Resolves: DevDisplay Idea Submission Feature Request Author: AadityaHande --- IDEA_SUBMISSION_FEATURE.md | 183 +++++++++ backend/app.js | 4 + backend/controllers/ideas.controllers.js | 341 ++++++++++++++++ backend/models/ideas.models.js | 171 ++++++++ backend/routes/ideas.routes.js | 43 ++ src/Page/IdeaSubmission.jsx | 271 +++++++++++-- .../IdeaSubmission/CollaborationHub.jsx | 380 ++++++++++++++++++ src/components/IdeaSubmission/IdeaCard.jsx | 205 ++++++++++ .../IdeaSubmission/IdeaSubmissionForm.jsx | 285 +++++++++++++ .../IdeaSubmission/SubmissionStatus.jsx | 88 ++++ .../IdeaSubmission/TrendingIdeas.jsx | 150 +++++++ 11 files changed, 2094 insertions(+), 27 deletions(-) create mode 100644 IDEA_SUBMISSION_FEATURE.md create mode 100644 backend/controllers/ideas.controllers.js create mode 100644 backend/models/ideas.models.js create mode 100644 backend/routes/ideas.routes.js create mode 100644 src/components/IdeaSubmission/CollaborationHub.jsx create mode 100644 src/components/IdeaSubmission/IdeaCard.jsx create mode 100644 src/components/IdeaSubmission/IdeaSubmissionForm.jsx create mode 100644 src/components/IdeaSubmission/SubmissionStatus.jsx create mode 100644 src/components/IdeaSubmission/TrendingIdeas.jsx diff --git a/IDEA_SUBMISSION_FEATURE.md b/IDEA_SUBMISSION_FEATURE.md new file mode 100644 index 00000000..a740224a --- /dev/null +++ b/IDEA_SUBMISSION_FEATURE.md @@ -0,0 +1,183 @@ +# 💡 Idea Submission Feature - DevDisplay + +## Overview +This feature implements a comprehensive idea submission and voting system for DevDisplay, allowing users to submit innovative project ideas, vote on their favorites, and collaborate on top-voted ideas. + +## 🚀 Features Implemented + +### 1. **Idea Submission System** +- **Monthly Submission Window**: Users can submit ideas only during the first week of each month +- **One Idea Per User**: Each user can submit only one idea per month +- **Rich Form Data**: Includes title, description, tags, resources needed, and media URLs +- **Real-time Validation**: Prevents duplicate submissions and enforces submission periods + +### 2. **Voting System** +- **Democratic Voting**: Users can vote for their favorite ideas +- **One Vote Per Idea**: Each user can only vote once per idea +- **Real-time Updates**: Vote counts update immediately +- **Leaderboard**: Top ideas are ranked by vote count + +### 3. **Trending Ideas Dashboard** +- **Top 5 Trending**: Displays the most popular ideas dynamically +- **Leaderboard View**: Shows ranking with medal system (🥇🥈🥉) +- **Real-time Updates**: Trending list updates as votes come in + +### 4. **Collaboration Hub** +- **Selected Ideas**: Shows ideas chosen for development +- **Role-based Collaboration**: Users can join projects in specific roles +- **Team Management**: View current team members and their roles +- **Project Status Tracking**: Track development progress + +### 5. **User Experience Features** +- **Responsive Design**: Works on all device sizes +- **Animated Interactions**: Smooth animations with Framer Motion +- **Real-time Status**: Live countdown timers and submission status +- **Comprehensive Navigation**: Easy tab-based navigation + +## 🛠️ Technical Implementation + +### Backend (Node.js + Express + MongoDB) + +**Models:** +- `Ideas Model` (ideas.models.js): Complete data structure for ideas with voting and collaboration features + +**Controllers:** +- `Ideas Controller` (ideas.controllers.js): Handles all idea-related operations including submission, voting, and collaboration + +**Routes:** +- `Ideas Routes` (ideas.routes.js): RESTful API endpoints for all functionality + +**Key API Endpoints:** +``` +GET /devdisplay/v1/ideas/status - Get submission status +GET /devdisplay/v1/ideas/trending - Get trending ideas +GET /devdisplay/v1/ideas/current - Get current month ideas +GET /devdisplay/v1/ideas/:ideaId - Get specific idea +POST /devdisplay/v1/ideas/submit - Submit new idea +POST /devdisplay/v1/ideas/:ideaId/vote - Vote for idea +DELETE /devdisplay/v1/ideas/:ideaId/vote - Remove vote +PUT /devdisplay/v1/ideas/:ideaId/select - Select idea for development +POST /devdisplay/v1/ideas/:ideaId/collaborate - Join collaboration +``` + +### Frontend (React + Tailwind CSS + Framer Motion) + +**Main Components:** +- `IdeaSubmission.jsx`: Main page with tab navigation +- `IdeaSubmissionForm.jsx`: Form for submitting new ideas +- `IdeaCard.jsx`: Individual idea display with voting +- `TrendingIdeas.jsx`: Trending ideas dashboard with leaderboard +- `SubmissionStatus.jsx`: Real-time submission status with countdown +- `CollaborationHub.jsx`: Collaboration and team management + +**Key Features:** +- Real-time countdown timers +- Interactive voting system +- Responsive grid layouts +- Smooth animations and transitions +- Form validation and error handling + +## 📋 Workflow + +### Monthly Cycle: +1. **Week 1**: Idea submission window opens +2. **Weeks 2-4**: Community voting phase +3. **End of Month**: Top idea selected for development +4. **Next Month**: Community collaboration on selected idea + +### User Journey: +1. **Submit**: Users submit innovative ideas during submission week +2. **Vote**: Community votes on their favorite ideas throughout the month +3. **Collaborate**: Users join development of selected ideas +4. **Recognize**: Contributors receive badges and recognition + +## 🎯 Benefits + +### For Contributors: +- **Skill Building**: Hands-on experience in collaborative development +- **Recognition**: Badges, leaderboard rankings, and portfolio credits +- **Networking**: Connect with other developers and build relationships +- **Innovation**: Platform to share and develop creative ideas + +### For Community: +- **Quality Ideas**: Democratic voting ensures best ideas rise to top +- **Active Participation**: Encourages ongoing community engagement +- **Real Projects**: Ideas become actual implemented projects +- **Learning**: Collaborative development teaches new skills + +## 🔧 Setup Instructions + +### Backend Setup: +1. Ensure MongoDB is connected (already configured in DevDisplay) +2. The Ideas model will be automatically registered when first used +3. API routes are already integrated into the main app.js + +### Frontend Setup: +1. All components are already integrated into the main IdeaSubmission page +2. Navigation is handled through the existing App.js routing +3. No additional dependencies needed (uses existing React, Tailwind, Framer Motion) + +## 🎨 Design Features + +### Visual Elements: +- **Gradient Backgrounds**: Beautiful blue-purple gradients +- **Interactive Cards**: Hover effects and animations +- **Status Indicators**: Color-coded status badges and indicators +- **Responsive Layout**: Mobile-first design approach +- **Emoji Integration**: Fun and engaging emoji usage throughout + +### User Feedback: +- **Real-time Updates**: Immediate feedback on actions +- **Loading States**: Smooth loading animations +- **Error Handling**: Clear error messages and guidance +- **Success Messages**: Celebratory success confirmations + +## 🚀 Future Enhancements + +### Potential Additions: +- **Idea Categories**: Filter ideas by category/technology +- **Search Functionality**: Search through submitted ideas +- **Idea Comments**: Allow discussion on specific ideas +- **Progress Tracking**: Kanban boards for development progress +- **Notification System**: Email/push notifications for important events +- **Idea History**: Archive of past implemented ideas +- **Advanced Analytics**: Detailed statistics and insights + +### Admin Features: +- **Moderation Tools**: Ability to moderate submitted ideas +- **Selection Override**: Manual selection of ideas for development +- **User Management**: Manage user permissions and roles +- **Analytics Dashboard**: Comprehensive analytics and reporting + +## 📊 Metrics & Success Indicators + +### Engagement Metrics: +- Number of ideas submitted per month +- Community voting participation rates +- Collaboration join rates +- Project completion rates + +### Quality Metrics: +- Idea implementation success rate +- Community satisfaction scores +- Feature usage analytics +- User retention in collaborative projects + +## 🤝 Contributing + +To enhance this feature: +1. Follow the existing code structure and patterns +2. Maintain the responsive design principles +3. Add comprehensive error handling +4. Include appropriate animations and user feedback +5. Test all functionality thoroughly + +## 📝 Notes + +- **Database**: Uses MongoDB with Mongoose ODM +- **Authentication**: Currently uses email-based identification (can be enhanced with proper auth) +- **Validation**: Both frontend and backend validation implemented +- **Security**: Basic input sanitization and validation +- **Performance**: Optimized queries and pagination support + +This implementation provides a solid foundation for the idea submission feature that can be extended and customized based on community needs and feedback. \ No newline at end of file diff --git a/backend/app.js b/backend/app.js index 775f9731..0a5576f7 100644 --- a/backend/app.js +++ b/backend/app.js @@ -2,6 +2,7 @@ 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'; const app = express(); @@ -15,6 +16,9 @@ app.use('/devdisplay/v1/trending/dev', devRouter); // Use the githubRouter for the "/api/v1/trending/github" route app.use('/devdisplay/v1/trending/github', githubRouter); +// Use the ideasRouter for the "/devdisplay/v1/ideas" route +app.use('/devdisplay/v1/ideas', ideasRouter); + app.use('/devdisplay/v1/subscribers', getSubscribers); app.get('/', (req, res) => { diff --git a/backend/controllers/ideas.controllers.js b/backend/controllers/ideas.controllers.js new file mode 100644 index 00000000..9581d1d0 --- /dev/null +++ b/backend/controllers/ideas.controllers.js @@ -0,0 +1,341 @@ +import Ideas from '../models/ideas.models.js'; +import { apiResponse } from '../utils/response.utils.js'; + +// Submit a new idea +export const submitIdea = async (req, res) => { + try { + const { title, description, submittedBy, submitterEmail, tags, resourcesNeeded, mediaUrls } = req.body; + + // Check if submission is open + if (!Ideas.isSubmissionOpen()) { + return res.status(400).json( + apiResponse(400, null, 'Idea submission is only allowed during the first week of each month') + ); + } + + // Get current submission period + const { month, year } = Ideas.getCurrentSubmissionPeriod(); + + // Check if user already submitted an idea this month + const existingIdea = await Ideas.findOne({ + submitterEmail, + submissionMonth: month, + submissionYear: year + }); + + if (existingIdea) { + return res.status(400).json( + apiResponse(400, null, 'You can only submit one idea per month') + ); + } + + // Create new idea + const newIdea = new Ideas({ + title, + description, + submittedBy, + submitterEmail, + tags: tags || [], + resourcesNeeded, + mediaUrls: mediaUrls || [], + submissionMonth: month, + submissionYear: year + }); + + await newIdea.save(); + + res.status(201).json( + apiResponse(201, newIdea, 'Idea submitted successfully') + ); + } catch (error) { + console.error('Error submitting idea:', error); + res.status(500).json( + apiResponse(500, null, 'Internal server error') + ); + } +}; + +// Get all ideas for current month +export const getCurrentMonthIdeas = async (req, res) => { + try { + const { month, year } = Ideas.getCurrentSubmissionPeriod(); + const { page = 1, limit = 10, sortBy = 'votes', order = 'desc' } = req.query; + + const skip = (page - 1) * limit; + const sortOrder = order === 'desc' ? -1 : 1; + + const ideas = await Ideas.find({ + submissionMonth: month, + submissionYear: year, + isArchived: false + }) + .sort({ [sortBy]: sortOrder, createdAt: -1 }) + .skip(skip) + .limit(parseInt(limit)) + .select('-voters'); // Don't expose voter details + + const totalIdeas = await Ideas.countDocuments({ + submissionMonth: month, + submissionYear: year, + isArchived: false + }); + + res.status(200).json( + apiResponse(200, { + ideas, + currentPage: parseInt(page), + totalPages: Math.ceil(totalIdeas / limit), + totalIdeas, + submissionPeriod: `${month} ${year}`, + isSubmissionOpen: Ideas.isSubmissionOpen() + }, 'Ideas retrieved successfully') + ); + } catch (error) { + console.error('Error getting ideas:', error); + res.status(500).json( + apiResponse(500, null, 'Internal server error') + ); + } +}; + +// Get trending ideas +export const getTrendingIdeas = async (req, res) => { + try { + const { limit = 5 } = req.query; + const trendingIdeas = await Ideas.getTrendingIdeas(parseInt(limit)); + + res.status(200).json( + apiResponse(200, trendingIdeas, 'Trending ideas retrieved successfully') + ); + } catch (error) { + console.error('Error getting trending ideas:', error); + res.status(500).json( + apiResponse(500, null, 'Internal server error') + ); + } +}; + +// Vote for an idea +export const voteForIdea = async (req, res) => { + try { + const { ideaId } = req.params; + const { userEmail } = req.body; + + if (!userEmail) { + return res.status(400).json( + apiResponse(400, null, 'User email is required') + ); + } + + const idea = await Ideas.findById(ideaId); + if (!idea) { + return res.status(404).json( + apiResponse(404, null, 'Idea not found') + ); + } + + // Check if user can vote (hasn't voted already) + if (!idea.canUserVote(userEmail)) { + return res.status(400).json( + apiResponse(400, null, 'You have already voted for this idea') + ); + } + + // Add vote + idea.addVote(userEmail); + await idea.save(); + + res.status(200).json( + apiResponse(200, { votes: idea.votes }, 'Vote added successfully') + ); + } catch (error) { + console.error('Error voting for idea:', error); + res.status(500).json( + apiResponse(500, null, 'Internal server error') + ); + } +}; + +// Remove vote from an idea +export const removeVoteFromIdea = async (req, res) => { + try { + const { ideaId } = req.params; + const { userEmail } = req.body; + + if (!userEmail) { + return res.status(400).json( + apiResponse(400, null, 'User email is required') + ); + } + + const idea = await Ideas.findById(ideaId); + if (!idea) { + return res.status(404).json( + apiResponse(404, null, 'Idea not found') + ); + } + + // Remove vote + const voteRemoved = idea.removeVote(userEmail); + if (!voteRemoved) { + return res.status(400).json( + apiResponse(400, null, 'You have not voted for this idea') + ); + } + + await idea.save(); + + res.status(200).json( + apiResponse(200, { votes: idea.votes }, 'Vote removed successfully') + ); + } catch (error) { + console.error('Error removing vote:', error); + res.status(500).json( + apiResponse(500, null, 'Internal server error') + ); + } +}; + +// Get idea by ID +export const getIdeaById = async (req, res) => { + try { + const { ideaId } = req.params; + + const idea = await Ideas.findById(ideaId) + .populate('collaborators', 'name email role') + .select('-voters'); // Don't expose voter details + + if (!idea) { + return res.status(404).json( + apiResponse(404, null, 'Idea not found') + ); + } + + res.status(200).json( + apiResponse(200, idea, 'Idea retrieved successfully') + ); + } catch (error) { + console.error('Error getting idea:', error); + res.status(500).json( + apiResponse(500, null, 'Internal server error') + ); + } +}; + +// Select idea for development (admin function) +export const selectIdeaForDevelopment = async (req, res) => { + try { + const { ideaId } = req.params; + + const idea = await Ideas.findById(ideaId); + if (!idea) { + return res.status(404).json( + apiResponse(404, null, 'Idea not found') + ); + } + + // Mark as selected for development + idea.status = 'selected'; + idea.selectedForDevelopment = true; + idea.developmentStartDate = new Date(); + + await idea.save(); + + res.status(200).json( + apiResponse(200, idea, 'Idea selected for development') + ); + } catch (error) { + console.error('Error selecting idea:', error); + res.status(500).json( + apiResponse(500, null, 'Internal server error') + ); + } +}; + +// Join idea collaboration +export const joinIdeaCollaboration = async (req, res) => { + try { + const { ideaId } = req.params; + const { name, email, role } = req.body; + + const idea = await Ideas.findById(ideaId); + if (!idea) { + return res.status(404).json( + apiResponse(404, null, 'Idea not found') + ); + } + + if (idea.status !== 'selected' && idea.status !== 'in_development') { + return res.status(400).json( + apiResponse(400, null, 'This idea is not open for collaboration yet') + ); + } + + // Check if user already joined + const existingCollaborator = idea.collaborators.find( + collaborator => collaborator.email === email + ); + + if (existingCollaborator) { + return res.status(400).json( + apiResponse(400, null, 'You have already joined this project') + ); + } + + // Add collaborator + idea.collaborators.push({ name, email, role }); + if (idea.status === 'selected') { + idea.status = 'in_development'; + } + + await idea.save(); + + res.status(200).json( + apiResponse(200, idea.collaborators, 'Successfully joined the collaboration') + ); + } catch (error) { + console.error('Error joining collaboration:', error); + res.status(500).json( + apiResponse(500, null, 'Internal server error') + ); + } +}; + +// Get submission status +export const getSubmissionStatus = async (req, res) => { + try { + const { userEmail } = req.query; + const { month, year } = Ideas.getCurrentSubmissionPeriod(); + const isSubmissionOpen = Ideas.isSubmissionOpen(); + + let hasSubmittedThisMonth = false; + if (userEmail) { + const existingIdea = await Ideas.findOne({ + submitterEmail: userEmail, + submissionMonth: month, + submissionYear: year + }); + hasSubmittedThisMonth = !!existingIdea; + } + + // Calculate days left for submission + const now = new Date(); + const currentDay = now.getDate(); + const daysLeftForSubmission = isSubmissionOpen ? 7 - currentDay + 1 : 0; + + res.status(200).json( + apiResponse(200, { + isSubmissionOpen, + submissionPeriod: `${month} ${year}`, + hasSubmittedThisMonth, + daysLeftForSubmission, + submissionEndsAt: isSubmissionOpen ? new Date(now.getFullYear(), now.getMonth(), 7, 23, 59, 59) : null + }, 'Submission status retrieved successfully') + ); + } catch (error) { + console.error('Error getting submission status:', error); + res.status(500).json( + apiResponse(500, null, 'Internal server error') + ); + } +}; \ No newline at end of file diff --git a/backend/models/ideas.models.js b/backend/models/ideas.models.js new file mode 100644 index 00000000..33601f5b --- /dev/null +++ b/backend/models/ideas.models.js @@ -0,0 +1,171 @@ +import mongoose from 'mongoose'; + +const ideaSchema = new mongoose.Schema({ + title: { + type: String, + required: true, + trim: true, + maxlength: 100 + }, + description: { + type: String, + required: true, + trim: true, + maxlength: 1000 + }, + submittedBy: { + type: String, + required: true, + trim: true + }, + submitterEmail: { + type: String, + required: true, + trim: true + }, + tags: [{ + type: String, + trim: true + }], + resourcesNeeded: { + type: String, + trim: true, + maxlength: 500 + }, + mediaUrls: [{ + type: String, + trim: true + }], + votes: { + type: Number, + default: 0 + }, + voters: [{ + email: { + type: String, + required: true + }, + votedAt: { + type: Date, + default: Date.now + } + }], + submissionMonth: { + type: String, + required: true + }, + submissionYear: { + type: Number, + required: true + }, + status: { + type: String, + enum: ['submitted', 'selected', 'in_development', 'completed'], + default: 'submitted' + }, + selectedForDevelopment: { + type: Boolean, + default: false + }, + developmentStartDate: { + type: Date + }, + collaborators: [{ + name: String, + email: String, + role: String, + joinedAt: { + type: Date, + default: Date.now + } + }], + tasks: [{ + title: String, + description: String, + assignedTo: String, + status: { + type: String, + enum: ['open', 'in_progress', 'completed'], + default: 'open' + }, + createdAt: { + type: Date, + default: Date.now + } + }], + isArchived: { + type: Boolean, + default: false + } +}, { + timestamps: true +}); + +// Index for efficient querying +ideaSchema.index({ submissionMonth: 1, submissionYear: 1 }); +ideaSchema.index({ votes: -1 }); +ideaSchema.index({ status: 1 }); + +// Virtual for getting submission period +ideaSchema.virtual('submissionPeriod').get(function() { + return `${this.submissionMonth}-${this.submissionYear}`; +}); + +// Method to check if user can vote +ideaSchema.methods.canUserVote = function(userEmail) { + return !this.voters.some(voter => voter.email === userEmail); +}; + +// Method to add vote +ideaSchema.methods.addVote = function(userEmail) { + if (this.canUserVote(userEmail)) { + this.voters.push({ email: userEmail }); + this.votes += 1; + return true; + } + return false; +}; + +// Method to remove vote +ideaSchema.methods.removeVote = function(userEmail) { + const voterIndex = this.voters.findIndex(voter => voter.email === userEmail); + if (voterIndex > -1) { + this.voters.splice(voterIndex, 1); + this.votes -= 1; + return true; + } + return false; +}; + +// Static method to get current submission period +ideaSchema.statics.getCurrentSubmissionPeriod = function() { + const now = new Date(); + const month = now.toLocaleString('default', { month: 'long' }); + const year = now.getFullYear(); + return { month, year }; +}; + +// Static method to check if submission is open +ideaSchema.statics.isSubmissionOpen = function() { + const now = new Date(); + const dayOfMonth = now.getDate(); + // Submission open during first 7 days of month + return dayOfMonth <= 7; +}; + +// Static method to get trending ideas +ideaSchema.statics.getTrendingIdeas = function(limit = 5) { + const { month, year } = this.getCurrentSubmissionPeriod(); + return this.find({ + submissionMonth: month, + submissionYear: year, + isArchived: false + }) + .sort({ votes: -1, createdAt: -1 }) + .limit(limit) + .populate('collaborators', 'name email role'); +}; + +const Ideas = mongoose.model('Ideas', ideaSchema); + +export default Ideas; \ No newline at end of file diff --git a/backend/routes/ideas.routes.js b/backend/routes/ideas.routes.js new file mode 100644 index 00000000..78358019 --- /dev/null +++ b/backend/routes/ideas.routes.js @@ -0,0 +1,43 @@ +import express from 'express'; +import { + submitIdea, + getCurrentMonthIdeas, + getTrendingIdeas, + voteForIdea, + removeVoteFromIdea, + getIdeaById, + selectIdeaForDevelopment, + joinIdeaCollaboration, + getSubmissionStatus +} from '../controllers/ideas.controllers.js'; + +const router = express.Router(); + +// Get submission status +router.get('/status', getSubmissionStatus); + +// Get trending ideas +router.get('/trending', getTrendingIdeas); + +// Get current month ideas +router.get('/current', getCurrentMonthIdeas); + +// Get specific idea by ID +router.get('/:ideaId', getIdeaById); + +// Submit a new idea +router.post('/submit', submitIdea); + +// Vote for an idea +router.post('/:ideaId/vote', voteForIdea); + +// Remove vote from an idea +router.delete('/:ideaId/vote', removeVoteFromIdea); + +// Select idea for development (admin route) +router.put('/:ideaId/select', selectIdeaForDevelopment); + +// Join idea collaboration +router.post('/:ideaId/collaborate', joinIdeaCollaboration); + +export { router as ideasRouter }; \ No newline at end of file diff --git a/src/Page/IdeaSubmission.jsx b/src/Page/IdeaSubmission.jsx index 0c136589..cda75c29 100644 --- a/src/Page/IdeaSubmission.jsx +++ b/src/Page/IdeaSubmission.jsx @@ -1,33 +1,250 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; +import IdeaSubmissionForm from '../components/IdeaSubmission/IdeaSubmissionForm'; +import IdeaCard from '../components/IdeaSubmission/IdeaCard'; +import TrendingIdeas from '../components/IdeaSubmission/TrendingIdeas'; +import SubmissionStatus from '../components/IdeaSubmission/SubmissionStatus'; +import CollaborationHub from '../components/IdeaSubmission/CollaborationHub'; const IdeaSubmissionPage = () => { + const [activeTab, setActiveTab] = useState('submit'); + const [ideas, setIdeas] = useState([]); + const [trendingIdeas, setTrendingIdeas] = useState([]); + const [submissionStatus, setSubmissionStatus] = useState(null); + const [loading, setLoading] = useState(true); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + + const tabs = [ + { id: 'submit', label: '💡 Submit Idea', icon: '🚀' }, + { id: 'browse', label: '📋 Browse Ideas', icon: '👀' }, + { id: 'trending', label: '🔥 Trending', icon: '📈' }, + { id: 'collaborate', label: '🤝 Collaborate', icon: '⚡' } + ]; + + useEffect(() => { + fetchSubmissionStatus(); + fetchTrendingIdeas(); + if (activeTab === 'browse') { + fetchCurrentMonthIdeas(); + } + }, [activeTab, currentPage]); + + const fetchSubmissionStatus = async () => { + try { + const response = await fetch('/devdisplay/v1/ideas/status'); + const data = await response.json(); + if (data.success) { + setSubmissionStatus(data.data); + } + } catch (error) { + console.error('Error fetching submission status:', error); + } + }; + + const fetchTrendingIdeas = async () => { + try { + const response = await fetch('/devdisplay/v1/ideas/trending?limit=5'); + const data = await response.json(); + if (data.success) { + setTrendingIdeas(data.data); + } + } catch (error) { + console.error('Error fetching trending ideas:', error); + } + }; + + const fetchCurrentMonthIdeas = async () => { + setLoading(true); + try { + const response = await fetch(`/devdisplay/v1/ideas/current?page=${currentPage}&limit=10`); + const data = await response.json(); + if (data.success) { + setIdeas(data.data.ideas); + setTotalPages(data.data.totalPages); + } + } catch (error) { + console.error('Error fetching ideas:', error); + } finally { + setLoading(false); + } + }; + + const handleIdeaSubmitted = () => { + fetchSubmissionStatus(); + fetchTrendingIdeas(); + if (activeTab === 'browse') { + fetchCurrentMonthIdeas(); + } + }; + + const handleVoteUpdate = (ideaId, newVoteCount) => { + setIdeas(prevIdeas => + prevIdeas.map(idea => + idea._id === ideaId ? { ...idea, votes: newVoteCount } : idea + ) + ); + fetchTrendingIdeas(); // Update trending as well + }; + return ( -
-
-

- This is the Idea Submission page - Want to Build this page as a contributor -

-
-

Click here for features details 👇🏻

- - - -

Benefits of Contribution

- - - +
+ {/* Header */} +
+
+ + 💡 Idea Submission Hub + + + Submit your innovative project ideas, vote for favorites, and collaborate with the community + to bring top-voted ideas to life! + + + {/* Submission Status Banner */} + {submissionStatus && ( + + )} +
+
+ + {/* Navigation Tabs */} +
+
+
+ {tabs.map((tab) => ( + + ))} +
+
+
+ + {/* Content */} +
+ {activeTab === 'submit' && ( + + + + )} + + {activeTab === 'browse' && ( + +
+

+ 📋 Browse Current Month Ideas +

+ {submissionStatus && ( +

+ Submission Period: {submissionStatus.submissionPeriod} +

+ )} +
+ + {loading ? ( +
+ {[1, 2, 3, 4, 5, 6].map((i) => ( +
+
+
+
+
+ ))} +
+ ) : ideas.length > 0 ? ( + <> +
+ {ideas.map((idea) => ( + + ))} +
+ + {/* Pagination */} + {totalPages > 1 && ( +
+ + + Page {currentPage} of {totalPages} + + +
+ )} + + ) : ( +
+
💡
+

No Ideas Yet

+

+ Be the first to submit an innovative idea this month! +

+
+ )} +
+ )} + + {activeTab === 'trending' && ( + + + + )} + + {activeTab === 'collaborate' && ( + + + + )} +
); }; diff --git a/src/components/IdeaSubmission/CollaborationHub.jsx b/src/components/IdeaSubmission/CollaborationHub.jsx new file mode 100644 index 00000000..ac41fb33 --- /dev/null +++ b/src/components/IdeaSubmission/CollaborationHub.jsx @@ -0,0 +1,380 @@ +import React, { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; + +const CollaborationHub = () => { + const [selectedIdeas, setSelectedIdeas] = useState([]); + const [loading, setLoading] = useState(true); + const [joinForm, setJoinForm] = useState({ + name: '', + email: '', + role: '', + ideaId: '' + }); + const [showJoinForm, setShowJoinForm] = useState(false); + const [joinMessage, setJoinMessage] = useState(''); + + const roles = [ + { value: 'Project Lead', icon: '👑', description: 'Lead the project and coordinate efforts' }, + { value: 'Frontend Developer', icon: '🎨', description: 'Work on user interface and experience' }, + { value: 'Backend Developer', icon: '⚙️', description: 'Handle server-side logic and APIs' }, + { value: 'Designer', icon: '🎭', description: 'Create designs and user experience' }, + { value: 'DevOps Engineer', icon: '🔧', description: 'Handle deployment and infrastructure' }, + { value: 'QA Tester', icon: '🐛', description: 'Test functionality and find bugs' }, + { value: 'Documentation', icon: '📚', description: 'Write documentation and guides' }, + { value: 'Marketing', icon: '📢', description: 'Help with promotion and outreach' } + ]; + + useEffect(() => { + fetchSelectedIdeas(); + }, []); + + const fetchSelectedIdeas = async () => { + setLoading(true); + try { + // For now, we'll fetch current month ideas and filter for selected ones + // In a real implementation, you'd have a dedicated endpoint for selected ideas + const response = await fetch('/devdisplay/v1/ideas/current?limit=50'); + const data = await response.json(); + + if (data.success) { + const selected = data.data.ideas.filter( + idea => idea.status === 'selected' || idea.status === 'in_development' + ); + setSelectedIdeas(selected); + } + } catch (error) { + console.error('Error fetching selected ideas:', error); + } finally { + setLoading(false); + } + }; + + const handleJoinCollaboration = async (e) => { + e.preventDefault(); + setJoinMessage(''); + + try { + const response = await fetch(`/devdisplay/v1/ideas/${joinForm.ideaId}/collaborate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: joinForm.name, + email: joinForm.email, + role: joinForm.role + }), + }); + + const data = await response.json(); + + if (data.success) { + setJoinMessage('🎉 Successfully joined the collaboration! Welcome aboard!'); + setShowJoinForm(false); + setJoinForm({ name: '', email: '', role: '', ideaId: '' }); + fetchSelectedIdeas(); // Refresh the list + } else { + setJoinMessage(`❌ ${data.message || 'Failed to join collaboration'}`); + } + } catch (error) { + console.error('Error joining collaboration:', error); + setJoinMessage('❌ An error occurred. Please try again.'); + } + }; + + const openJoinForm = (ideaId) => { + setJoinForm(prev => ({ ...prev, ideaId })); + setShowJoinForm(true); + setJoinMessage(''); + }; + + if (loading) { + return ( +
+
+ Loading collaboration opportunities... +
+ ); + } + + return ( + + {/* Header */} +
+

+ 🤝 Collaboration Hub +

+

+ Join forces with other developers to bring selected ideas to life! +

+
+ + {/* Join Form Modal */} + {showJoinForm && ( + + +

+ 🚀 Join Collaboration +

+ + {joinMessage && ( +
+ {joinMessage} +
+ )} + +
+
+ + setJoinForm(prev => ({ ...prev, name: e.target.value }))} + required + className="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="Your full name" + /> +
+ +
+ + setJoinForm(prev => ({ ...prev, email: e.target.value }))} + required + className="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="your.email@example.com" + /> +
+ +
+ + +
+ +
+ + +
+
+
+
+ )} + + {/* Selected Ideas for Collaboration */} + {selectedIdeas.length > 0 ? ( +
+ {selectedIdeas.map((idea, index) => ( + + {/* Project Header */} +
+
+
+ + {idea.status === 'selected' ? '🌟' : '🚧'} + +

+ {idea.title} +

+
+

+ by {idea.submittedBy} • {idea.votes} votes +

+ + {idea.status === 'selected' ? '🌟 Selected for Development' : '🚧 In Development'} + +
+ + openJoinForm(idea._id)} + className="bg-gradient-to-r from-blue-600 to-purple-600 text-white px-6 py-3 rounded-lg font-bold hover:from-blue-700 hover:to-purple-700 transition-all duration-300" + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + 🚀 Join Collaboration + +
+ + {/* Project Description */} +
+

📝 Project Description

+

+ {idea.description} +

+
+ + {/* Resources Needed */} + {idea.resourcesNeeded && ( +
+

🛠️ Skills & Resources Needed

+

+ {idea.resourcesNeeded} +

+
+ )} + + {/* Tags */} + {idea.tags && idea.tags.length > 0 && ( +
+

🏷️ Technologies

+
+ {idea.tags.map((tag, tagIndex) => ( + + #{tag} + + ))} +
+
+ )} + + {/* Current Collaborators */} +
+

+ 👥 Current Team ({idea.collaborators?.length || 0}) +

+ {idea.collaborators && idea.collaborators.length > 0 ? ( +
+ {idea.collaborators.map((collaborator, collabIndex) => ( +
+
+ {collaborator.name.charAt(0).toUpperCase()} +
+
+

+ {collaborator.name} +

+

+ {collaborator.role} +

+
+
+ ))} +
+ ) : ( +
+
👥
+

No collaborators yet. Be the first to join!

+
+ )} +
+
+ ))} +
+ ) : ( +
+
🚀
+

+ No Projects Available for Collaboration +

+

+ Ideas are selected for collaboration at the end of each month based on votes. +

+
+

How Collaboration Works:

+
+

1. 💡 Submit ideas during the first week of the month

+

2. 👍 Community votes on their favorite ideas

+

3. 🏆 Top-voted idea is selected for development

+

4. 🤝 Community collaborates to bring the idea to life

+
+
+
+ )} + + {/* Available Roles Info */} + +

+ 🎯 Available Collaboration Roles +

+
+ {roles.map((role, index) => ( +
+
{role.icon}
+

+ {role.value} +

+

+ {role.description} +

+
+ ))} +
+
+
+ ); +}; + +export default CollaborationHub; \ No newline at end of file diff --git a/src/components/IdeaSubmission/IdeaCard.jsx b/src/components/IdeaSubmission/IdeaCard.jsx new file mode 100644 index 00000000..9041809e --- /dev/null +++ b/src/components/IdeaSubmission/IdeaCard.jsx @@ -0,0 +1,205 @@ +import React, { useState } from 'react'; +import { motion } from 'framer-motion'; + +const IdeaCard = ({ idea, onVoteUpdate }) => { + const [isVoting, setIsVoting] = useState(false); + const [userEmail, setUserEmail] = useState(''); + const [showVoteInput, setShowVoteInput] = useState(false); + + const handleVote = async () => { + if (!userEmail) { + setShowVoteInput(true); + return; + } + + setIsVoting(true); + try { + const response = await fetch(`/devdisplay/v1/ideas/${idea._id}/vote`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ userEmail }), + }); + + const data = await response.json(); + + if (data.success) { + onVoteUpdate(idea._id, data.data.votes); + setShowVoteInput(false); + setUserEmail(''); + } else { + alert(data.message || 'Failed to vote'); + } + } catch (error) { + console.error('Error voting:', error); + alert('An error occurred while voting'); + } finally { + setIsVoting(false); + } + }; + + const formatDate = (dateString) => { + return new Date(dateString).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }); + }; + + return ( + + {/* Header */} +
+
+

+ {idea.title} +

+
+ 👤 {idea.submittedBy} + 📅 {formatDate(idea.createdAt)} +
+
+ + {/* Vote Button */} +
+ + {isVoting ? ( + + + + + ) : ( + 👍 + )} + + {idea.votes} +
+
+ + {/* Vote Input */} + {showVoteInput && ( + + +
+ setUserEmail(e.target.value)} + placeholder="your.email@example.com" + className="flex-1 px-3 py-2 bg-gray-600 border border-gray-500 rounded text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + +
+
+ )} + + {/* Description */} +

+ {idea.description} +

+ + {/* Tags */} + {idea.tags && idea.tags.length > 0 && ( +
+ {idea.tags.map((tag, index) => ( + + #{tag} + + ))} +
+ )} + + {/* Resources Needed */} + {idea.resourcesNeeded && ( +
+

🛠️ Resources Needed:

+

+ {idea.resourcesNeeded} +

+
+ )} + + {/* Status Badge */} +
+ + {idea.status === 'submitted' && '📝 Submitted'} + {idea.status === 'selected' && '🌟 Selected'} + {idea.status === 'in_development' && '🚧 In Development'} + {idea.status === 'completed' && '✅ Completed'} + + + {/* Collaborators count */} + {idea.collaborators && idea.collaborators.length > 0 && ( + + 👥 {idea.collaborators.length} collaborator{idea.collaborators.length !== 1 ? 's' : ''} + + )} +
+ + {/* Media Preview */} + {idea.mediaUrls && idea.mediaUrls.length > 0 && ( +
+
+ {idea.mediaUrls.slice(0, 3).map((url, index) => ( + + 🖼️ Media {index + 1} + + ))} + {idea.mediaUrls.length > 3 && ( + + +{idea.mediaUrls.length - 3} more + + )} +
+
+ )} +
+ ); +}; + +export default IdeaCard; \ No newline at end of file diff --git a/src/components/IdeaSubmission/IdeaSubmissionForm.jsx b/src/components/IdeaSubmission/IdeaSubmissionForm.jsx new file mode 100644 index 00000000..1db9a8cf --- /dev/null +++ b/src/components/IdeaSubmission/IdeaSubmissionForm.jsx @@ -0,0 +1,285 @@ +import React, { useState } from 'react'; +import { motion } from 'framer-motion'; + +const IdeaSubmissionForm = ({ onIdeaSubmitted }) => { + const [formData, setFormData] = useState({ + title: '', + description: '', + submittedBy: '', + submitterEmail: '', + tags: '', + resourcesNeeded: '', + mediaUrls: '' + }); + const [isSubmitting, setIsSubmitting] = useState(false); + const [submitMessage, setSubmitMessage] = useState(''); + + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData(prev => ({ + ...prev, + [name]: value + })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setIsSubmitting(true); + setSubmitMessage(''); + + try { + // Convert tags and media URLs from strings to arrays + const tagsArray = formData.tags + .split(',') + .map(tag => tag.trim()) + .filter(tag => tag.length > 0); + + const mediaUrlsArray = formData.mediaUrls + .split(',') + .map(url => url.trim()) + .filter(url => url.length > 0); + + const submitData = { + ...formData, + tags: tagsArray, + mediaUrls: mediaUrlsArray + }; + + const response = await fetch('/devdisplay/v1/ideas/submit', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(submitData), + }); + + const data = await response.json(); + + if (data.success) { + setSubmitMessage('🎉 Idea submitted successfully! Thank you for your contribution.'); + setFormData({ + title: '', + description: '', + submittedBy: '', + submitterEmail: '', + tags: '', + resourcesNeeded: '', + mediaUrls: '' + }); + if (onIdeaSubmitted) { + onIdeaSubmitted(); + } + } else { + setSubmitMessage(`❌ ${data.message || 'Failed to submit idea. Please try again.'}`); + } + } catch (error) { + console.error('Error submitting idea:', error); + setSubmitMessage('❌ An error occurred. Please try again.'); + } finally { + setIsSubmitting(false); + } + }; + + return ( + +
+
+

+ 🚀 Submit Your Innovative Idea +

+

+ Share your creative project ideas with the community. One idea per month per user. +

+
+ + {submitMessage && ( + + {submitMessage} + + )} + +
+ {/* Title */} +
+ + +

+ {formData.title.length}/100 characters +

+
+ + {/* Description */} +
+ +