diff --git a/src/App.jsx b/src/App.jsx index f688f76..341db12 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -25,6 +25,7 @@ const DashboardPage = lazy(() => import("./dashboard/DashboardPage")); const JourneyPage = lazy(() => import("./journey/JourneyPage")); const AchievementsPage = lazy(() => import("./achievements/AchievementsPage")); const CodingProfilePage = lazy(() => import("./coding/CodingProfilePage")); +const CareerPage = lazy(() => import("./career/CareerPage")); function RouteFallback() { return ( @@ -66,6 +67,7 @@ function AppRoutes() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/career/CareerPage.jsx b/src/career/CareerPage.jsx new file mode 100644 index 0000000..ce6a895 --- /dev/null +++ b/src/career/CareerPage.jsx @@ -0,0 +1,496 @@ +import React, { useEffect, useMemo, useState } from "react"; +import { Link } from "react-router-dom"; +import { profile } from "../data/profile"; +import PageHeader from "../components/ui/PageHeader"; +import SectionPanel from "../components/ui/SectionPanel"; +import LoadingState from "../components/ui/LoadingState"; +import ErrorState from "../components/ui/ErrorState"; +import EmptyState from "../components/ui/EmptyState"; +import { + formatDate, + getJournalProblems, + getJournalProjects, + getJournalStats, + getProblemLanguages, + getProblemSolvedAt, + getProblemTrackedCount, + isPlatformProfileSummary, + normalizePlatformName, + toProjectSlug, + uniqueValues, +} from "../lib/codingJournal"; + +function percent(value, total) { + if (!total) return "0%"; + return `${Math.min(100, Math.round((value / total) * 100))}%`; +} + +function useCareerData() { + const [state, setState] = useState({ + stats: null, + problems: [], + projects: [], + loading: true, + error: "", + }); + + useEffect(() => { + let ignore = false; + + Promise.all([getJournalStats(), getJournalProblems(), getJournalProjects()]) + .then(([statsData, problemsData, projectsData]) => { + if (ignore) return; + setState({ + stats: statsData ?? null, + problems: Array.isArray(problemsData) ? problemsData : [], + projects: Array.isArray(projectsData) ? projectsData : [], + loading: false, + error: "", + }); + }) + .catch((fetchError) => { + if (ignore) return; + setState({ + stats: null, + problems: [], + projects: [], + loading: false, + error: fetchError.message || "Unable to load career data from coding-journal.", + }); + }); + + return () => { + ignore = true; + }; + }, []); + + return state; +} + +export default function CareerPage() { + const { stats, problems, projects, loading, error } = useCareerData(); + + const analytics = useMemo(() => { + const profileSummaries = problems.filter((problem) => isPlatformProfileSummary(problem)); + const trackedProblems = problems.filter((problem) => !isPlatformProfileSummary(problem)); + const totalTrackedProblems = + trackedProblems.length + + profileSummaries.reduce((total, problem) => total + getProblemTrackedCount(problem), 0); + const verifiedSolutions = + stats?.verifiedProblems ?? trackedProblems.filter((problem) => problem.verified).length; + const platforms = uniqueValues(problems.map((problem) => normalizePlatformName(problem.platform))).filter(Boolean); + const languages = uniqueValues( + trackedProblems.flatMap((problem) => getProblemLanguages(problem)) + ).filter(Boolean); + const platformSnapshot = ["LeetCode", "HackerRank", "CodeChef", "Codeforces"].map((name) => ({ + name, + count: problems.reduce((total, problem) => { + if (normalizePlatformName(problem.platform) !== name) return total; + return total + getProblemTrackedCount(problem); + }, 0), + })); + + const today = new Date(); + today.setHours(0, 0, 0, 0); + const startDate = new Date(today); + startDate.setDate(startDate.getDate() - 364); + const contributions = {}; + + const addEvent = (rawDate) => { + if (!rawDate) return; + const date = new Date(rawDate); + if (Number.isNaN(date.getTime())) return; + date.setHours(0, 0, 0, 0); + if (date < startDate || date > today) return; + const key = date.toISOString().slice(0, 10); + contributions[key] = (contributions[key] || 0) + 1; + }; + + trackedProblems.forEach((problem) => addEvent(getProblemSolvedAt(problem))); + projects.forEach((project) => addEvent(project.updatedAt)); + + const heatmapDays = Array.from({ length: 365 }, (_, index) => { + const date = new Date(startDate); + date.setDate(startDate.getDate() + index); + const key = date.toISOString().slice(0, 10); + return contributions[key] || 0; + }); + + const currentStreak = (() => { + let streak = 0; + for (let i = heatmapDays.length - 1; i >= 0; i -= 1) { + if (heatmapDays[i] > 0) streak += 1; + else break; + } + return streak; + })(); + + const longestStreak = heatmapDays.reduce( + (state, count) => { + if (count > 0) { + const current = state.current + 1; + return { current, longest: Math.max(state.longest, current) }; + } + return { current: 0, longest: state.longest }; + }, + { current: 0, longest: 0 } + ).longest; + + const technologyPool = [ + ...projects.flatMap((project) => [project.language, ...(project.topics || [])]), + ...trackedProblems.flatMap((problem) => getProblemLanguages(problem)), + ...trackedProblems.flatMap((problem) => problem.tags || []), + ] + .filter(Boolean) + .map((item) => String(item).toLowerCase()); + + const countMatches = (patterns) => + technologyPool.filter((item) => patterns.some((pattern) => item.includes(pattern))).length; + + const skillsMatrix = [ + { + name: "Frontend", + detail: "React, UI systems, responsive implementation", + count: countMatches(["react", "javascript", "typescript", "frontend", "css", "html"]), + }, + { + name: "Backend", + detail: "Node.js workflows, APIs, and automation tooling", + count: countMatches(["node", "api", "backend", "server", "express"]), + }, + { + name: "Problem Solving", + detail: "DSA practice, platform breadth, and verified solutions", + count: totalTrackedProblems, + }, + { + name: "Git / GitHub", + detail: "Repository history, release hygiene, and synced work", + count: projects.length + countMatches(["git", "github"]), + }, + { + name: "Databases", + detail: "Data modeling exposure and full-stack project workflows", + count: countMatches(["database", "mongodb", "sql", "postgres", "mysql"]), + }, + ]; + + const maxSkillCount = Math.max(...skillsMatrix.map((item) => item.count), 1); + const normalizedSkills = skillsMatrix.map((item) => ({ + ...item, + percentage: percent(item.count, maxSkillCount), + })); + + const achievements = []; + if (verifiedSolutions >= 1) { + achievements.push({ + date: trackedProblems + .filter((problem) => problem.verified) + .map((problem) => new Date(getProblemSolvedAt(problem) || problem.updatedAt || 0)) + .filter((date) => !Number.isNaN(date.getTime())) + .sort((a, b) => a - b)[0] || null, + title: "First verified solution", + description: `${verifiedSolutions} verified solutions are now tracked in coding-journal.`, + }); + } + if (totalTrackedProblems >= 10) { + achievements.push({ + date: null, + title: "10 tracked problems", + description: `${totalTrackedProblems} tracked problems across coding platforms.`, + }); + } + if (projects.length >= 1) { + achievements.push({ + date: new Date( + projects + .map((project) => new Date(project.updatedAt || 0)) + .filter((date) => !Number.isNaN(date.getTime())) + .sort((a, b) => a - b)[0] || 0 + ), + title: "Active GitHub project history", + description: `${projects.length} synced GitHub repositories support the portfolio.`, + }); + } + + const timelineEvents = [ + ...projects + .filter((project) => project.updatedAt) + .map((project) => ({ + date: new Date(project.updatedAt), + title: `${project.name} updated`, + description: "Repository update captured from the live GitHub-synced project feed.", + link: `/projects/${toProjectSlug(project.name)}`, + badge: project.language || "Project", + })), + ...trackedProblems + .filter((problem) => getProblemSolvedAt(problem)) + .map((problem) => ({ + date: new Date(getProblemSolvedAt(problem)), + title: `${problem.title} solved`, + description: `${normalizePlatformName(problem.platform)} activity recorded in coding-journal.`, + link: `/coding`, + badge: normalizePlatformName(problem.platform), + })), + ...achievements + .filter((achievement) => achievement.date && !Number.isNaN(achievement.date.getTime())) + .map((achievement) => ({ + date: achievement.date, + title: achievement.title, + description: achievement.description, + link: "/career", + badge: "Achievement", + })), + ] + .filter((event) => event.date && !Number.isNaN(event.date.getTime())) + .sort((a, b) => b.date.getTime() - a.date.getTime()) + .slice(0, 8); + + return { + totalTrackedProblems, + verifiedSolutions, + platforms, + languages, + githubProjects: stats?.totalProjects ?? projects.length, + platformSnapshot, + skillsMatrix: normalizedSkills, + currentStreak, + longestStreak, + timelineEvents, + }; + }, [problems, projects, stats]); + + return ( +
+ + + {loading ? ( + + + + ) : error ? ( + + + + ) : !problems.length && !projects.length ? ( + + + + ) : ( + <> + +
+
+
+ Open To Work + Available +
+

{profile.role}

+

{profile.status}

+

{profile.location}

+
+
+
+ Current Focus +
+

MCA @ Bangalore University

+

Building recruiter-trustworthy frontend and full-stack work through live projects, coding practice, and public proof systems.

+
+
+
+ + +
+
+ Total Tracked Problems + {analytics.totalTrackedProblems} +
+
+ Verified Solutions + {analytics.verifiedSolutions} +
+
+ Platforms + {analytics.platforms.length} +
+
+ Languages + {analytics.languages.length} +
+
+ GitHub Projects + {analytics.githubProjects} +
+
+
+ + +
+ {analytics.platformSnapshot.map((item) => ( +
+
+ {item.name} + {item.count} +
+

{item.count} tracked

+

{item.count ? "Live coding-journal activity is available for this platform." : "No synced activity yet for this platform."}

+
+ ))} +
+
+ + +
+
+
+ {analytics.skillsMatrix.map((skill) => ( +
+
+ {skill.name} + {skill.percentage} +
+
+ +
+

{skill.detail}

+
+ ))} +
+
+
+
+ + +
+ {[ + { + title: "Remote Internship", + detail: "A strong fit for frontend, product implementation, and developer-tool workflows.", + }, + { + title: "Frontend Developer", + detail: "React UI systems, responsive implementation, dashboard views, and premium portfolio polish.", + }, + { + title: "Full Stack Developer", + detail: "Frontend delivery plus Node.js workflows, synced content, and practical product features.", + }, + { + title: "Freelance Projects", + detail: "Portfolio upgrades, coding dashboards, route fixes, UI cleanup, and build-safe feature work.", + }, + ].map((item) => ( +
+
+ Open +
+

{item.title}

+

{item.detail}

+
+ ))} +
+
+ + +
+
+

Download the resume or reach out directly

+

Use the resume for a quick overview, or get in touch for internship, frontend, full-stack, or freelance opportunities.

+
+
+ + Download Resume + + + Contact Me + +
+
+
+ + +
+ {analytics.timelineEvents.length ? ( + analytics.timelineEvents.map((event) => ( +
+ {formatDate(event.date)} +
+
+

{event.title}

+ {event.badge} +
+

{event.description}

+ + View Details + +
+
+ )) + ) : ( +
+

No recent career timeline events yet

+

The timeline will populate automatically from coding-journal and project updates.

+
+ )} +
+
+ + +
+
+

Open to internships, developer roles, and freelance work

+

I’m looking for opportunities where clean implementation, live proof of work, and steady engineering progress matter.

+
+
+ + Contact Me + + + View Projects + +
+
+
+ + )} +
+ ); +} diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index fa2ca1f..1b885d3 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -23,6 +23,7 @@ export default function Navbar() { if (id === "Home") return location.pathname === "/"; if (id === "Projects") return location.pathname.startsWith("/projects"); if (id === "Coding") return location.pathname.startsWith("/coding"); + if (id === "Career") return location.pathname.startsWith("/career"); if (id === "Dashboard") { return ( location.pathname.startsWith("/dashboard") || @@ -51,6 +52,8 @@ export default function Navbar() { navigate("/about"); } else if (id === "Coding") { navigate("/coding"); + } else if (id === "Career") { + navigate("/career"); } else if (id === "Dashboard") { navigate("/dashboard"); } else if (id === "Contact") { @@ -100,6 +103,7 @@ export default function Navbar() { "Home", "Projects", "Coding", + "Career", "Dashboard", "About", "Contact",