diff --git a/backend/src/main/java/com/rit/portal/controller/CollabController.java b/backend/src/main/java/com/rit/portal/controller/CollabController.java index c0c27a3..927223d 100644 --- a/backend/src/main/java/com/rit/portal/controller/CollabController.java +++ b/backend/src/main/java/com/rit/portal/controller/CollabController.java @@ -32,6 +32,16 @@ public class CollabController { return collabRequestRepository.findAllByOrderByCreatedAtDesc(); } + @GetMapping("/my-requests") + public List getMyCollabRequests(@RequestParam String email) { + return collabRequestRepository.findByAuthorEmailOrderByCreatedAtDesc(email); + } + + @GetMapping("/my-applications") + public List getMyCollabApplications(@RequestParam String email) { + return collabApplicationRepository.findByApplicantEmailOrderByCreatedAtDesc(email); + } + @GetMapping("/{id}") public ResponseEntity getCollabRequestById(@PathVariable Integer id) { return collabRequestRepository.findById(id) @@ -39,6 +49,11 @@ public class CollabController { .orElse(ResponseEntity.notFound().build()); } + @GetMapping("/{id}/applications") + public List getApplicationsForRequest(@PathVariable Integer id) { + return collabApplicationRepository.findByCollabRequestIdOrderByCreatedAtDesc(id); + } + @PostMapping public CollabRequest createCollabRequest(@RequestBody CollabRequest request) { if (request.getStatus() == null) request.setStatus("OPEN"); @@ -51,6 +66,22 @@ public class CollabController { return collabRequestRepository.save(request); } + @PutMapping("/{id}") + public ResponseEntity updateCollabRequest( + @PathVariable Integer id, + @RequestBody CollabRequest updated) { + return collabRequestRepository.findById(id).map(req -> { + if (updated.getProjectIdea() != null) req.setProjectIdea(updated.getProjectIdea()); + if (updated.getTag() != null) req.setTag(updated.getTag()); + if (updated.getDepartment() != null) req.setDepartment(updated.getDepartment()); + if (updated.getYear() != null) req.setYear(updated.getYear()); + if (updated.getGithubLink() != null) req.setGithubLink(updated.getGithubLink()); + if (updated.getContactInfo() != null) req.setContactInfo(updated.getContactInfo()); + if (updated.getCollaboratorsNeeded() != null) req.setCollaboratorsNeeded(updated.getCollaboratorsNeeded()); + return ResponseEntity.ok(collabRequestRepository.save(req)); + }).orElse(ResponseEntity.notFound().build()); + } + @PostMapping("/{id}/apply") public ResponseEntity applyToCollabRequest( @PathVariable Integer id, @@ -66,7 +97,7 @@ public class CollabController { CollabApplication savedApp = collabApplicationRepository.save(application); - // Increment count on parent request + // Increment applications count on parent request request.setApplicationsCount(request.getApplicationsCount() + 1); collabRequestRepository.save(request); @@ -83,6 +114,7 @@ public class CollabController { payload.put("telegram_chat_id", request.getTelegramChatId()); payload.put("discord_user_id", request.getDiscordUserId()); payload.put("applicant_name", savedApp.getApplicantName()); + payload.put("applicant_email", savedApp.getApplicantEmail()); payload.put("applicant_dept", savedApp.getApplicantDept()); payload.put("applicant_year", savedApp.getApplicantYear()); payload.put("applicant_contact", savedApp.getApplicantContact()); @@ -108,18 +140,24 @@ public class CollabController { app.setStatus(newStatus); CollabApplication updated = collabApplicationRepository.save(app); - // Delete request entirely when accepted count meets requested collaborators count - if ("ACCEPTED".equals(newStatus) && !"ACCEPTED".equals(oldStatus)) { - CollabRequest parentReq = app.getCollabRequest(); - if (parentReq != null) { + // Manage parent request counts + CollabRequest parentReq = app.getCollabRequest(); + if (parentReq != null) { + if ("ACCEPTED".equals(newStatus) && !"ACCEPTED".equals(oldStatus)) { int accepted = (parentReq.getAcceptedCount() == null ? 0 : parentReq.getAcceptedCount()) + 1; parentReq.setAcceptedCount(accepted); int needed = parentReq.getCollaboratorsNeeded() == null ? 1 : parentReq.getCollaboratorsNeeded(); if (accepted >= needed) { - collabRequestRepository.delete(parentReq); - } else { - collabRequestRepository.save(parentReq); + parentReq.setStatus("CLOSED"); } + collabRequestRepository.save(parentReq); + } else if (!"ACCEPTED".equals(newStatus) && "ACCEPTED".equals(oldStatus)) { + int accepted = Math.max(0, (parentReq.getAcceptedCount() == null ? 0 : parentReq.getAcceptedCount()) - 1); + parentReq.setAcceptedCount(accepted); + if ("CLOSED".equals(parentReq.getStatus())) { + parentReq.setStatus("OPEN"); + } + collabRequestRepository.save(parentReq); } } return ResponseEntity.ok(updated); diff --git a/backend/src/main/java/com/rit/portal/entity/CollabApplication.java b/backend/src/main/java/com/rit/portal/entity/CollabApplication.java index c7a2d95..f59d484 100644 --- a/backend/src/main/java/com/rit/portal/entity/CollabApplication.java +++ b/backend/src/main/java/com/rit/portal/entity/CollabApplication.java @@ -26,6 +26,9 @@ public class CollabApplication { @Column(name = "applicant_name", nullable = false) private String applicantName; + @Column(name = "applicant_email") + private String applicantEmail; + @Column(name = "applicant_dept", nullable = false) private String applicantDept; diff --git a/backend/src/main/java/com/rit/portal/repository/CollabApplicationRepository.java b/backend/src/main/java/com/rit/portal/repository/CollabApplicationRepository.java index f40b6ec..7dc40a2 100644 --- a/backend/src/main/java/com/rit/portal/repository/CollabApplicationRepository.java +++ b/backend/src/main/java/com/rit/portal/repository/CollabApplicationRepository.java @@ -4,6 +4,10 @@ import com.rit.portal.entity.CollabApplication; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface CollabApplicationRepository extends JpaRepository { + List findByApplicantEmailOrderByCreatedAtDesc(String applicantEmail); + List findByCollabRequestIdOrderByCreatedAtDesc(Integer collabRequestId); } diff --git a/backend/src/main/java/com/rit/portal/repository/CollabRequestRepository.java b/backend/src/main/java/com/rit/portal/repository/CollabRequestRepository.java index 7e382c6..6b337d0 100644 --- a/backend/src/main/java/com/rit/portal/repository/CollabRequestRepository.java +++ b/backend/src/main/java/com/rit/portal/repository/CollabRequestRepository.java @@ -8,6 +8,7 @@ import java.util.List; @Repository public interface CollabRequestRepository extends JpaRepository { List findAllByOrderByCreatedAtDesc(); + List findByAuthorEmailOrderByCreatedAtDesc(String authorEmail); List findByTelegramChatIdAndStatusOrderByCreatedAtDesc(Long telegramChatId, String status); List findByDiscordUserIdAndStatusOrderByCreatedAtDesc(String discordUserId, String status); } diff --git a/src/pages/DevCollab/DevCollab.tsx b/src/pages/DevCollab/DevCollab.tsx index 8335e11..a7876ba 100644 --- a/src/pages/DevCollab/DevCollab.tsx +++ b/src/pages/DevCollab/DevCollab.tsx @@ -1,10 +1,11 @@ import { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { - Code2, Plus, GitBranch, Tag, User, Send, CheckCircle2, - Search, Filter, Sparkles, X, ExternalLink, Layers, - ChevronRight, MessageSquare, Clock, Users, ArrowUpRight, - ShieldCheck, LogIn, ShieldAlert + Code2, Plus, GitBranch, CheckCircle2, XCircle, + Search, Sparkles, X, ExternalLink, + ChevronRight, MessageSquare, ArrowUpRight, + ShieldCheck, LogIn, ShieldAlert, Edit3, Trash2, Users, + Check, Clock, Send } from 'lucide-react'; import { Link } from 'react-router-dom'; import { getBackendUrl } from '@/lib/utils'; @@ -13,6 +14,7 @@ import { useAuth } from '@/context/AuthContext'; export interface CollabRequestItem { id: number; authorName: string; + authorEmail?: string; department: string; year: string; projectIdea: string; @@ -28,6 +30,19 @@ export interface CollabRequestItem { createdAt: string; } +export interface CollabApplicationItem { + id: number; + applicantName: string; + applicantEmail?: string; + applicantDept: string; + applicantYear: string; + applicantContact: string; + message?: string; + status: string; // PENDING, ACCEPTED, REJECTED + createdAt: string; + collabRequest?: CollabRequestItem; +} + const TAG_OPTIONS = [ 'looking for co-developing a project from scratch', 'looking for beta testers', @@ -41,6 +56,7 @@ const INITIAL_MOCK_REQUESTS: CollabRequestItem[] = [ { id: 101, authorName: 'Rohan Sharma', + authorEmail: 'rohan@ritchennai.edu.in', department: 'CSE', year: '3rd Year', projectIdea: 'Building an automated AI Attendance & Proxy Detection system using OpenCV and Python for college labs.', @@ -56,6 +72,7 @@ const INITIAL_MOCK_REQUESTS: CollabRequestItem[] = [ { id: 102, authorName: 'Ananya V.', + authorEmail: 'ananya@ritchennai.edu.in', department: 'AIML', year: '2nd Year', projectIdea: 'Need beta testers for our web-based RIT Bus Tracking & Live ETA PWA before publishing to campus app store.', @@ -71,6 +88,7 @@ const INITIAL_MOCK_REQUESTS: CollabRequestItem[] = [ { id: 103, authorName: 'Karthik N.', + authorEmail: 'karthik@ritchennai.edu.in', department: 'ECE', year: '4th Year', projectIdea: 'Open-source IoT Smart Canteen Pre-order Hardware & Mobile App. Looking for React Native & ESP32 contributors!', @@ -88,18 +106,25 @@ const INITIAL_MOCK_REQUESTS: CollabRequestItem[] = [ export default function DevCollab() { const { user, isAuthenticated, isVerifiedStudent, loginWithGoogle } = useAuth(); + const [activeTab, setActiveTab] = useState<'explore' | 'my-requests' | 'my-applications'>('explore'); const [requests, setRequests] = useState(INITIAL_MOCK_REQUESTS); + const [myRequests, setMyRequests] = useState([]); + const [myApplications, setMyApplications] = useState([]); const [loading, setLoading] = useState(false); const [activeTagFilter, setActiveTagFilter] = useState('All'); const [searchTerm, setSearchTerm] = useState(''); - // Modals + // Modals & UI state const [showPostModal, setShowPostModal] = useState(false); + const [editingCollab, setEditingCollab] = useState(null); const [selectedCollab, setSelectedCollab] = useState(null); + const [managingCollab, setManagingCollab] = useState(null); + const [managingApplicants, setManagingApplicants] = useState([]); + const [loadingApplicants, setLoadingApplicants] = useState(false); const [toastMessage, setToastMessage] = useState(null); const [showAuthGate, setShowAuthGate] = useState(false); - // New Request Form — auto-populated from Google profile for verified students + // New Request Form const [newAuthorName, setNewAuthorName] = useState(user?.name || ''); const [newDept, setNewDept] = useState('CSE'); const [newYear, setNewYear] = useState('1st Year'); @@ -110,14 +135,26 @@ export default function DevCollab() { const [newContact, setNewContact] = useState(user?.email || ''); const [submittingPost, setSubmittingPost] = useState(false); - // Application Form — auto-populated from Google profile + // Application Form const [appApplicantName, setAppApplicantName] = useState(user?.name || ''); const [appDept, setAppDept] = useState('CSE'); const [appYear, setAppYear] = useState('1st Year'); const [appContact, setAppContact] = useState(user?.email || ''); const [appMessage, setAppMessage] = useState(''); + const [submittingApp, setSubmittingApp] = useState(false); + + // Sync state when user logs in + useEffect(() => { + if (user?.name) { + setNewAuthorName(user.name); + setAppApplicantName(user.name); + } + if (user?.email) { + setNewContact(user.email); + setAppContact(user.email); + } + }, [user]); - // Helper: gate action behind verified student check const requireVerifiedStudent = (action: () => void) => { if (!isAuthenticated || !isVerifiedStudent) { setShowAuthGate(true); @@ -125,7 +162,11 @@ export default function DevCollab() { } action(); }; - const [submittingApp, setSubmittingApp] = useState(false); + + const showToast = (msg: string) => { + setToastMessage(msg); + setTimeout(() => setToastMessage(null), 4000); + }; const fetchCollabRequests = () => { setLoading(true); @@ -139,21 +180,78 @@ export default function DevCollab() { setRequests(data); } }) - .catch(() => { - // Fallback to mock data if backend not available - }) + .catch(() => {}) .finally(() => setLoading(false)); }; + const fetchMyRequests = () => { + if (!user?.email) return; + fetch(getBackendUrl(`/api/collab/my-requests?email=${encodeURIComponent(user.email)}`)) + .then((res) => res.json()) + .then((data) => { + if (Array.isArray(data)) { + setMyRequests(data); + } + }) + .catch(() => {}); + }; + + const fetchMyApplications = () => { + if (!user?.email) return; + fetch(getBackendUrl(`/api/collab/my-applications?email=${encodeURIComponent(user.email)}`)) + .then((res) => res.json()) + .then((data) => { + if (Array.isArray(data)) { + setMyApplications(data); + } + }) + .catch(() => {}); + }; + useEffect(() => { fetchCollabRequests(); }, []); - const showToast = (msg: string) => { - setToastMessage(msg); - setTimeout(() => setToastMessage(null), 4000); + useEffect(() => { + if (isAuthenticated && user?.email) { + fetchMyRequests(); + fetchMyApplications(); + } + }, [isAuthenticated, user?.email, activeTab]); + + // Open Manage Applicants Modal + const openManageApplicants = (item: CollabRequestItem) => { + setManagingCollab(item); + setLoadingApplicants(true); + fetch(getBackendUrl(`/api/collab/${item.id}/applications`)) + .then((res) => res.json()) + .then((data) => { + setManagingApplicants(Array.isArray(data) ? data : []); + }) + .catch(() => setManagingApplicants([])) + .finally(() => setLoadingApplicants(false)); }; + // Handle Accept / Reject applicant + const handleUpdateApplicationStatus = (appId: number, status: 'ACCEPTED' | 'REJECTED') => { + fetch(getBackendUrl(`/api/collab/applications/${appId}/status?status=${status}`), { + method: 'PUT', + }) + .then((res) => res.json()) + .then(() => { + showToast(status === 'ACCEPTED' ? '✅ Applicant ACCEPTED!' : '❌ Applicant REJECTED'); + if (managingCollab) { + openManageApplicants(managingCollab); + } + fetchCollabRequests(); + fetchMyRequests(); + }) + .catch(() => { + showToast('Updated status locally'); + }); + }; + + // Handle Post New Request const handlePostRequest = (e: React.FormEvent) => { e.preventDefault(); if (!newAuthorName.trim() || !newIdea.trim()) return; @@ -161,6 +259,7 @@ export default function DevCollab() { setSubmittingPost(true); const payload = { authorName: newAuthorName.trim(), + authorEmail: user?.email || null, department: newDept, year: newYear, tag: newTag, @@ -178,19 +277,18 @@ export default function DevCollab() { .then((res) => res.json()) .then((saved) => { setRequests((prev) => [saved, ...prev]); + setMyRequests((prev) => [saved, ...prev]); showToast('🎉 Collaboration request posted successfully!'); setShowPostModal(false); - setNewAuthorName(''); setNewIdea(''); setNewGithub(''); - setNewContact(''); setNewCollaboratorsNeeded(1); }) .catch(() => { - // Local state fallback const mockSaved: CollabRequestItem = { id: Date.now(), authorName: payload.authorName, + authorEmail: user?.email, department: payload.department, year: payload.year, tag: payload.tag, @@ -204,12 +302,56 @@ export default function DevCollab() { createdAt: new Date().toISOString(), }; setRequests((prev) => [mockSaved, ...prev]); - showToast('🎉 Collaboration request posted to live view!'); + setMyRequests((prev) => [mockSaved, ...prev]); + showToast('🎉 Collaboration request posted!'); setShowPostModal(false); }) .finally(() => setSubmittingPost(false)); }; + // Handle Edit Request + const handleSaveEditRequest = (e: React.FormEvent) => { + e.preventDefault(); + if (!editingCollab) return; + + fetch(getBackendUrl(`/api/collab/${editingCollab.id}`), { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(editingCollab), + }) + .then((res) => res.json()) + .then((updated) => { + setRequests((prev) => prev.map((r) => (r.id === updated.id ? updated : r))); + setMyRequests((prev) => prev.map((r) => (r.id === updated.id ? updated : r))); + showToast('✏️ Collaboration request updated!'); + setEditingCollab(null); + }) + .catch(() => { + setRequests((prev) => prev.map((r) => (r.id === editingCollab.id ? editingCollab : r))); + setMyRequests((prev) => prev.map((r) => (r.id === editingCollab.id ? editingCollab : r))); + showToast('✏️ Request updated locally!'); + setEditingCollab(null); + }); + }; + + // Handle Delete Request + const handleDeleteRequest = (id: number) => { + if (!confirm('Are you sure you want to delete this collaboration request?')) return; + + fetch(getBackendUrl(`/api/collab/${id}`), { method: 'DELETE' }) + .then(() => { + setRequests((prev) => prev.filter((r) => r.id !== id)); + setMyRequests((prev) => prev.filter((r) => r.id !== id)); + showToast('🗑️ Collaboration request deleted!'); + }) + .catch(() => { + setRequests((prev) => prev.filter((r) => r.id !== id)); + setMyRequests((prev) => prev.filter((r) => r.id !== id)); + showToast('🗑️ Request removed!'); + }); + }; + + // Handle Apply const handleApply = (e: React.FormEvent) => { e.preventDefault(); if (!selectedCollab || !appApplicantName.trim() || !appContact.trim()) return; @@ -217,6 +359,7 @@ export default function DevCollab() { setSubmittingApp(true); const payload = { applicantName: appApplicantName.trim(), + applicantEmail: user?.email || null, applicantDept: appDept, applicantYear: appYear, applicantContact: appContact.trim(), @@ -237,9 +380,8 @@ export default function DevCollab() { ) ); setSelectedCollab(null); - setAppApplicantName(''); - setAppContact(''); setAppMessage(''); + fetchMyApplications(); }) .catch(() => { showToast(`🚀 Application submitted! Sent notification to ${selectedCollab.authorName}.`); @@ -271,12 +413,6 @@ export default function DevCollab() { return (
- {/* Background Orbs */} -
-
-
-
- {/* Toast Notification */} {toastMessage && ( @@ -313,7 +449,7 @@ export default function DevCollab() {

- Post your open-source projects, search for co-developers, or find beta testers. Connect instantly via Telegram & Discord bot integrations! + Post open-source projects, search for co-developers, or manage candidate applications cleanly on the website or via Telegram!

@@ -321,7 +457,7 @@ export default function DevCollab() { whileHover={{ scale: 1.03 }} whileTap={{ scale: 0.97 }} onClick={() => requireVerifiedStudent(() => setShowPostModal(true))} - className="flex items-center gap-2.5 px-6 py-3.5 rounded-2xl text-white font-semibold text-sm shadow-lg shadow-orange-500/25 shrink-0" + className="flex items-center gap-2.5 px-6 py-3.5 rounded-2xl text-white font-semibold text-sm shadow-lg shadow-orange-500/25 shrink-0 cursor-pointer" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', fontFamily: 'Poppins, sans-serif' }} > {isVerifiedStudent && } @@ -329,196 +465,615 @@ export default function DevCollab() { Post Collaboration Request
+ + {/* Dashboard Tabs Bar */} +
+ + + {isAuthenticated && ( + <> + + + + + )} +
{/* Main Content Container */}
- {/* Telegram / Discord Bot Banner Callout */} -
-
-
- + {/* TAB 1: EXPLORE PROJECTS */} + {activeTab === 'explore' && ( + <> + {/* Telegram Callout Banner */} +
+
+
+ +
+
+

+ 🤖 Telegram Bot Workflow +

+

+ Applications sent to your projects will also arrive on Telegram with [Accept] and [Reject] buttons for 1-tap responses! +

+
+
+ +
-
-

- 🤖 Post or remove requests via Telegram or Discord! -

-

- Send /collab to post requests or /remove to cancel active requests anytime! -

-
-
- -
- - {/* Search & Tag Filter Bar */} -
- {/* Search Input */} -
- - setSearchTerm(e.target.value)} - className="w-full pl-11 pr-4 py-3 rounded-2xl bg-[#F8FAFC] border-none text-sm text-[#1E293B] placeholder-[#94A3B8] focus:ring-2 focus:ring-[#F97316]/20 focus:outline-none transition-all" - style={{ fontFamily: 'Inter, sans-serif' }} - /> -
- - {/* Tag Filter Pills */} -
- {['All', ...TAG_OPTIONS].map((tag) => { - const isActive = activeTagFilter === tag; - return ( - - ); - })} -
-
+ /> +
- {/* Cards Grid */} - {filteredRequests.length > 0 ? ( -
- {filteredRequests.map((item) => { - const tagStyle = getTagColor(item.tag); - const needed = item.collaboratorsNeeded || 1; - const accepted = item.acceptedCount || 0; - const isClosed = item.status === 'CLOSED' || item.status === 'CANCELLED' || accepted >= needed; +
+ {['All', ...TAG_OPTIONS].map((tag) => { + const isActive = activeTagFilter === tag; + return ( + + ); + })} +
+
- return ( - -
- {/* Top Bar: Tag Badge & Status */} -
- - - {item.tag} - + {/* Cards Grid */} + {filteredRequests.length > 0 ? ( +
+ {filteredRequests.map((item) => { + const tagStyle = getTagColor(item.tag); + const needed = item.collaboratorsNeeded || 1; + const accepted = item.acceptedCount || 0; + const isClosed = item.status === 'CLOSED' || item.status === 'CANCELLED' || accepted >= needed; - {isClosed ? ( - - CLOSED • SPOTS FILLED - - ) : ( - - {accepted} / {needed} Spots Filled - - )} -
+ return ( + +
+
+ + + {item.tag} + - {/* Project Idea */} -

- {item.projectIdea} -

-
- -
- {/* GitHub link if present */} - {item.githubLink && ( - - - View Repository - - - )} - - {/* Author Info */} -
-
-
- {item.authorName.charAt(0)} + {isClosed ? ( + + CLOSED • SPOTS FILLED + + ) : ( + + {accepted} / {needed} Spots Filled + + )}
-
-
{item.authorName}
-
- {item.department} • {item.year} + +

+ {item.projectIdea} +

+
+ +
+ {item.githubLink && ( + + + View Repository + + + )} + +
+
+
+ {item.authorName.charAt(0)} +
+
+
{item.authorName}
+
+ {item.department} • {item.year} +
+
+
+ +
+
+ {item.applicationsCount} Applications +
+ + {isClosed ? ( + + ) : ( + + )} +
+ + ); + })} +
+ ) : ( +
+ +

+ No collaboration requests found +

+

+ Be the first developer to post a request or try adjusting your filters! +

+ +
+ )} + + )} + + {/* TAB 2: MY POSTED REQUESTS */} + {activeTab === 'my-requests' && ( +
+
+
+

+ My Posted Projects ({myRequests.length}) +

+

+ Manage your posted projects, edit details, and accept/reject applicant candidates. +

+
+ + +
+ + {myRequests.length > 0 ? ( +
+ {myRequests.map((item) => ( +
+
+
+ + {item.tag} + + + {item.acceptedCount || 0} / {item.collaboratorsNeeded || 1} Spots Filled +
-
-
- {item.applicationsCount} Applications -
-
+

+ {item.projectIdea} +

- {/* Primary Action Button */} - {isClosed ? ( +
- ) : ( - - )} + +
+ + + +
+
- - ); - })} + ))} +
+ ) : ( +
+ +

You haven't posted any collaboration requests yet.

+

Post your first project to find co-developers across campus!

+ +
+ )}
- ) : ( -
- -

- No collaboration requests found -

-

- Be the first developer to post a request or try adjusting your filters! -

- + )} + + {/* TAB 3: MY SENT APPLICATIONS */} + {activeTab === 'my-applications' && ( +
+
+

+ My Sent Applications ({myApplications.length}) +

+

+ Track the status of your sent collaboration requests. Unlocks author contact info once accepted! +

+
+ + {myApplications.length > 0 ? ( +
+ {myApplications.map((app) => { + const status = app.status?.toUpperCase() || 'PENDING'; + const isAccepted = status === 'ACCEPTED'; + const isRejected = status === 'REJECTED'; + + return ( +
+
+
+ {isAccepted && ( + + ACCEPTED + + )} + {isRejected && ( + + REJECTED + + )} + {!isAccepted && !isRejected && ( + + PENDING REVIEW + + )} +
+ +

+ Applied to Project #{app.collabRequest?.id || ''} +

+ +

"{app.message || 'No additional message'}"

+
+ + {/* Unlocked Author Contact Card if Accepted */} + {isAccepted && app.collabRequest?.contactInfo && ( +
+ + 🎉 Author Unlocked Contact Info: + + + {app.collabRequest.contactInfo} + +
+ )} +
+ ); + })} +
+ ) : ( +
+ +

No sent applications found.

+

Browse projects in the Explore tab and apply to co-develop!

+ +
+ )}
)}
+ {/* ─── MANAGE APPLICANTS MODAL ─────────────────────────────────────── */} + + {managingCollab && ( +
+ setManagingCollab(null)} + className="absolute inset-0 bg-slate-900/60 backdrop-blur-xs" + /> + +
+
+ + Manage Candidates + +

+ Applicants for Project #{managingCollab.id} +

+
+ +
+ +
+ Project Idea: {managingCollab.projectIdea} +
+ + {loadingApplicants ? ( +
Loading candidate applications...
+ ) : managingApplicants.length > 0 ? ( +
+ {managingApplicants.map((applicant) => { + const status = applicant.status?.toUpperCase() || 'PENDING'; + const isAccepted = status === 'ACCEPTED'; + const isRejected = status === 'REJECTED'; + + return ( +
+
+
+ {applicant.applicantName} + + {applicant.applicantDept} • {applicant.applicantYear} • {applicant.applicantEmail || 'No email'} + +
+ + {isAccepted && ( + + ACCEPTED CANDIDATE + + )} + {isRejected && ( + + REJECTED + + )} +
+ + {applicant.message && ( +
+ "{applicant.message}" +
+ )} + +
+
+ Contact: {applicant.applicantContact} +
+ +
+ {!isAccepted && ( + + )} + + {!isRejected && ( + + )} +
+
+
+ ); + })} +
+ ) : ( +
+ No applications received for this project yet. +
+ )} +
+
+ )} +
+ + {/* ─── EDIT COLLABORATION REQUEST MODAL ─────────────────────────────── */} + + {editingCollab && ( +
+ setEditingCollab(null)} + className="absolute inset-0 bg-slate-900/60 backdrop-blur-xs" + /> + +
+

+ Edit Project Request #{editingCollab.id} +

+ +
+ +
+
+ +