diff --git a/package-lock.json b/package-lock.json
index 66fb7c1c..2b167f6b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23037,7 +23037,6 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
diff --git a/src/app/opportunities/page.tsx b/src/app/opportunities/page.tsx
new file mode 100644
index 00000000..4a95a97c
--- /dev/null
+++ b/src/app/opportunities/page.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import OpportunityDashboard from '@/components/features/OpportunityDashboard';
+
+export const metadata = {
+ title: 'Opportunities Hub | DevPath',
+ description: 'Explore career opportunities, developer internships, and hackathons with deadline countdowns and dynamic bookmarking.',
+};
+
+export default function OpportunitiesPage() {
+ return (
+
+
+ {/* Page Heading banner */}
+
+
+ Career & Developer Opportunities
+
+
+ Apply to top-tier internship openings, hackathons, and fellowship cohorts.
+ Pin key dates and visualize live deadline countdowns below.
+
+
+
+ {/* Dashboard Component */}
+
+
+
+ );
+}
diff --git a/src/components/features/OpportunityDashboard.tsx b/src/components/features/OpportunityDashboard.tsx
new file mode 100644
index 00000000..7cf09249
--- /dev/null
+++ b/src/components/features/OpportunityDashboard.tsx
@@ -0,0 +1,427 @@
+'use client';
+
+import React, { useState, useEffect, useMemo } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import {
+ Bookmark,
+ Calendar,
+ Grid,
+ List,
+ Search,
+ SlidersHorizontal,
+ Clock,
+ Building,
+ Check,
+ AlertTriangle,
+ ArrowUpDown,
+} from 'lucide-react';
+import { useBookmarks, BookmarkItem } from '@/hooks/useBookmarks';
+
+const MOCK_OPPORTUNITIES: BookmarkItem[] = [
+ {
+ id: 'opp-1',
+ title: 'Open Source Contributor',
+ description: 'GSoC is a global program focused on bringing student developers into open source software development.',
+ type: 'opportunity',
+ company: 'Google',
+ deadline: '2026-06-25T23:59:59Z',
+ tags: ['Open Source', 'Remote', 'Stipend'],
+ color: 'linear-gradient(135deg, #ea4335, #c5221f)',
+ },
+ {
+ id: 'opp-2',
+ title: 'Software Engineering Intern',
+ description: "Join Meta's product teams to build technologies that help people connect, find communities, and grow businesses.",
+ type: 'opportunity',
+ company: 'Meta',
+ deadline: '2026-06-20T23:59:59Z',
+ tags: ['Internship', 'Frontend', 'Backend'],
+ color: 'linear-gradient(135deg, #0080ff, #0055b3)',
+ },
+ {
+ id: 'opp-3',
+ title: 'Hackathon Participant',
+ description: 'Our annual 48-hour hackathon to build open-source projects for community welfare. Compete for cash prizes and mentorship.',
+ type: 'opportunity',
+ company: 'DevPath',
+ deadline: '2026-06-18T18:00:00Z',
+ tags: ['Hackathon', 'Community', 'Prizes'],
+ color: 'linear-gradient(135deg, #10b981, #047857)',
+ },
+ {
+ id: 'opp-4',
+ title: 'GitHub Octernship Fellow',
+ description: 'The GitHub Octernships program connects students with industry partners for paid internship opportunities.',
+ type: 'opportunity',
+ company: 'GitHub',
+ deadline: '2026-07-15T23:59:59Z',
+ tags: ['Fellowship', 'Remote', 'Paid'],
+ color: 'linear-gradient(135deg, #24292e, #1a1e22)',
+ },
+ {
+ id: 'opp-5',
+ title: 'Developer Participant',
+ description: 'Build next-generation payment integrations and financial tools using Stripe API.',
+ type: 'opportunity',
+ company: 'Stripe',
+ deadline: '2026-06-01T23:59:59Z',
+ tags: ['Hackathon', 'API', 'Payments'],
+ color: 'linear-gradient(135deg, #635bff, #4339ca)',
+ },
+];
+
+interface DeadlineStatus {
+ text: string;
+ status: 'expired' | 'closing-today' | 'closing-tomorrow' | 'upcoming';
+ daysLeft: number;
+}
+
+const calculateDeadlineStatus = (deadlineStr: string): DeadlineStatus => {
+ const deadline = new Date(deadlineStr).getTime();
+ const now = Date.now();
+ const diff = deadline - now;
+
+ if (diff <= 0) {
+ return { text: 'Expired', status: 'expired', daysLeft: -1 };
+ }
+
+ const oneDay = 24 * 60 * 60 * 1000;
+ const daysLeft = diff / oneDay;
+
+ if (daysLeft <= 1) {
+ return { text: 'Closing today', status: 'closing-today', daysLeft };
+ } else if (daysLeft <= 2) {
+ return { text: 'Closing tomorrow', status: 'closing-tomorrow', daysLeft };
+ } else {
+ return { text: `${Math.ceil(daysLeft)} days left`, status: 'upcoming', daysLeft };
+ }
+};
+
+export default function OpportunityDashboard() {
+ const { bookmarks, toggleBookmark, isBookmarked } = useBookmarks();
+ const [activeTab, setActiveTab] = useState<'explore' | 'bookmarked'>('explore');
+ const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
+ const [searchQuery, setSearchQuery] = useState('');
+ const [sortBy, setSortBy] = useState<'deadline' | 'recent' | 'alpha'>('deadline');
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ // Filter bookmarked opportunities to match our schema
+ const bookmarkedOpportunities = useMemo(() => {
+ return bookmarks.filter((b) => b.type === 'opportunity');
+ }, [bookmarks]);
+
+ // Combine mock data with bookmark updates to display correct saved states
+ const opportunitiesSource = useMemo(() => {
+ if (activeTab === 'bookmarked') {
+ return bookmarkedOpportunities;
+ }
+ return MOCK_OPPORTUNITIES;
+ }, [activeTab, bookmarkedOpportunities]);
+
+ // Handle Search & Filter logic
+ const processedOpportunities = useMemo(() => {
+ let result = [...opportunitiesSource];
+
+ // Search query match
+ if (searchQuery.trim()) {
+ const query = searchQuery.toLowerCase();
+ result = result.filter(
+ (opp) =>
+ opp.title.toLowerCase().includes(query) ||
+ opp.company?.toLowerCase().includes(query) ||
+ opp.tags?.some((t) => t.toLowerCase().includes(query))
+ );
+ }
+
+ // Sort matching configuration
+ result.sort((a, b) => {
+ if (sortBy === 'deadline') {
+ const timeA = a.deadline ? new Date(a.deadline).getTime() : Infinity;
+ const timeB = b.deadline ? new Date(b.deadline).getTime() : Infinity;
+ return timeA - timeB;
+ }
+ if (sortBy === 'recent') {
+ const bookmarkedA = a.bookmarkedAt ?? 0;
+ const bookmarkedB = b.bookmarkedAt ?? 0;
+ // Most recent first
+ return bookmarkedB - bookmarkedA;
+ }
+ if (sortBy === 'alpha') {
+ return a.title.localeCompare(b.title);
+ }
+ return 0;
+ });
+
+ return result;
+ }, [opportunitiesSource, searchQuery, sortBy]);
+
+ const handleBookmarkToggle = (opp: BookmarkItem) => {
+ const isSaved = isBookmarked(opp.id);
+ if (isSaved) {
+ toggleBookmark(opp);
+ } else {
+ toggleBookmark({
+ ...opp,
+ bookmarkedAt: Date.now(),
+ });
+ }
+ };
+
+ return (
+
+ {/* Header and Controls */}
+
+
+
+
+
+ Opportunity Hub
+
+
+ Track applications, countdown deadlines, and save high-value career opportunities.
+
+
+
+ {/* View and Sorting Actions */}
+
+ {/* Search Bar */}
+
+
+ setSearchQuery(e.target.value)}
+ className="w-full pl-10 pr-4 py-2 bg-slate-950/80 border border-slate-800 focus:border-primary/50 focus:outline-none rounded-xl text-xs text-white placeholder:text-slate-600 transition-colors"
+ />
+
+
+ {/* Sort Dropdown */}
+
+
+
setSortBy(e.target.value as any)}
+ className="bg-transparent text-xs text-slate-300 focus:outline-none cursor-pointer"
+ >
+ Closest Deadline
+ Recently Bookmarked
+ Alphabetical
+
+
+
+ {/* Grid/List toggler */}
+
+ setViewMode('grid')}
+ className={`p-1.5 rounded-lg transition-colors ${viewMode === 'grid' ? 'bg-primary text-white' : 'text-slate-500 hover:text-slate-300'}`}
+ title="Grid View"
+ aria-label="Switch to grid view"
+ >
+
+
+ setViewMode('list')}
+ className={`p-1.5 rounded-lg transition-colors ${viewMode === 'list' ? 'bg-primary text-white' : 'text-slate-500 hover:text-slate-300'}`}
+ title="List View"
+ aria-label="Switch to list view"
+ >
+
+
+
+
+
+
+ {/* Tabs */}
+
+ setActiveTab('explore')}
+ className={`pb-3 px-4 text-sm font-bold relative transition-colors ${
+ activeTab === 'explore' ? 'text-white' : 'text-slate-500 hover:text-slate-300'
+ }`}
+ >
+ Explore
+ {activeTab === 'explore' && (
+
+ )}
+
+ setActiveTab('bookmarked')}
+ className={`pb-3 px-4 text-sm font-bold relative transition-colors flex items-center gap-1.5 ${
+ activeTab === 'bookmarked' ? 'text-white' : 'text-slate-500 hover:text-slate-300'
+ }`}
+ >
+ My Bookmarks
+ {mounted && bookmarkedOpportunities.length > 0 && (
+
+ {bookmarkedOpportunities.length}
+
+ )}
+ {activeTab === 'bookmarked' && (
+
+ )}
+
+
+
+ {/* Dashboard Grid/List */}
+
+ {processedOpportunities.length === 0 ? (
+
+
+
+
+ No Opportunities Found
+
+ {activeTab === 'bookmarked'
+ ? 'Save opportunities from the Explore tab to track deadlines here.'
+ : 'No opportunities match your current filter query.'}
+
+
+ ) : (
+
+ {processedOpportunities.map((opp) => {
+ const saved = isBookmarked(opp.id);
+ const status = opp.deadline ? calculateDeadlineStatus(opp.deadline) : null;
+
+ // Deadline badge styling configuration
+ let badgeColor = 'bg-slate-900 text-slate-400 border-slate-800';
+ if (status) {
+ if (status.status === 'expired') {
+ badgeColor = 'bg-red-500/10 text-red-400 border-red-500/20';
+ } else if (status.status === 'closing-today') {
+ badgeColor = 'bg-orange-500/10 text-orange-400 border-orange-500/20 animate-pulse';
+ } else if (status.status === 'closing-tomorrow') {
+ badgeColor = 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20';
+ } else {
+ badgeColor = 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20';
+ }
+ }
+
+ return (
+
+ {/* Accent Side Line */}
+
+
+ {/* Body Content */}
+
+ {/* Header Row: Company and Title */}
+
+
+
+ {opp.company}
+
+
+ {opp.title}
+
+
+
+
+ {opp.description}
+
+
+ {/* Tags */}
+
+ {opp.tags?.map((tag) => (
+
+ {tag}
+
+ ))}
+
+
+
+ {/* Action Footer (List layout alignment) */}
+
+ {/* Deadline & Countdown */}
+ {opp.deadline && (
+
+
+
+
+ {new Date(opp.deadline).toLocaleDateString(undefined, {
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric',
+ })}
+
+
+ {mounted ? (
+
+
+ {status?.text}
+
+ ) : (
+
+ )}
+
+ )}
+
+ {/* Bookmark Toggle Button */}
+
handleBookmarkToggle(opp)}
+ className={`flex items-center gap-1.5 px-3.5 py-2 rounded-xl text-xs font-bold transition-all duration-300 w-full sm:w-auto justify-center ${
+ saved
+ ? 'bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20'
+ : 'bg-primary hover:bg-primary/95 text-white shadow-lg shadow-primary/10'
+ }`}
+ title={saved ? 'Remove from bookmarks' : 'Add to bookmarks'}
+ >
+ {saved ? (
+ <>
+
+ Unsave
+ >
+ ) : (
+ <>
+
+ Bookmark
+ >
+ )}
+
+
+
+ );
+ })}
+
+ )}
+
+
+ );
+}
diff --git a/src/components/features/SkillTreeVisualizer.tsx b/src/components/features/SkillTreeVisualizer.tsx
index ae308085..e0d39b53 100644
--- a/src/components/features/SkillTreeVisualizer.tsx
+++ b/src/components/features/SkillTreeVisualizer.tsx
@@ -149,6 +149,31 @@ export default function SkillTreeVisualizer({
window.removeEventListener('close-all-overlays', handleCloseAll);
}, []);
+ // Bind local arrow key shortcuts for node selection cycling
+ useKeyboardShortcuts({
+ arrowright: () => {
+ if (nodes.length === 0) return;
+ const currentIndex = selectedNode ? nodes.findIndex((n) => n.id === selectedNode.id) : -1;
+ const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % nodes.length;
+ setSelectedNode(nodes[nextIndex]);
+ },
+ arrowleft: () => {
+ if (nodes.length === 0) return;
+ const currentIndex = selectedNode ? nodes.findIndex((n) => n.id === selectedNode.id) : -1;
+ const prevIndex = currentIndex === -1 ? nodes.length - 1 : (currentIndex - 1 + nodes.length) % nodes.length;
+ setSelectedNode(nodes[prevIndex]);
+ },
+ });
+
+ // Listen for the escape close-all-overlays event to close the side drawer
+ useEffect(() => {
+ const handleCloseAll = () => {
+ setSelectedNode(null);
+ };
+ window.addEventListener('close-all-overlays', handleCloseAll);
+ return () => window.removeEventListener('close-all-overlays', handleCloseAll);
+ }, []);
+
return (
([]);
@@ -48,9 +50,12 @@ export function Leaderboard() {
#{i + 1}
-
{u.displayName ?? 'Anonymous'}
diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx
index d009cf30..1715e135 100644
--- a/src/components/layout/Navbar.tsx
+++ b/src/components/layout/Navbar.tsx
@@ -34,6 +34,7 @@ const navLinks = [
{ href: '/community', label: 'Community' },
{ href: '/resources', label: 'Resources' },
{ href: '/events', label: 'Events' },
+ { href: '/opportunities', label: 'Opportunities' },
{ href: '/opensource', label: 'Open Source' },
{ href: '/team', label: 'Team' },
];
diff --git a/src/hooks/useBookmarks.ts b/src/hooks/useBookmarks.ts
index 0ba63671..5fa57b22 100644
--- a/src/hooks/useBookmarks.ts
+++ b/src/hooks/useBookmarks.ts
@@ -4,9 +4,13 @@ export interface BookmarkItem {
id: string;
title: string;
description: string;
- type: 'roadmap' | 'project';
+ type: 'roadmap' | 'project' | 'opportunity';
color?: string;
path?: string;
+ company?: string;
+ deadline?: string;
+ tags?: string[];
+ bookmarkedAt?: number;
}
const listeners = new Set<() => void>();