352 lines
19 KiB
JavaScript
Raw Normal View History

import React, { useState, useEffect } from 'react';
2024-09-20 12:22:42 -05:00
import { ProgressBar } from 'primereact/progressbar';
import { Accordion, AccordionTab } from 'primereact/accordion';
2024-12-09 12:43:33 -06:00
import { useSession, signIn, getSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import { useBadge } from '@/hooks/badges/useBadge';
import GenericButton from '@/components/buttons/GenericButton';
2024-12-11 11:23:14 -06:00
import UserProgressFlow from './UserProgressFlow';
import { Tooltip } from 'primereact/tooltip';
import RepoSelector from '@/components/profile/RepoSelector';
import MoreInfo from '@/components/MoreInfo';
2024-09-20 12:22:42 -05:00
2024-09-27 13:33:27 -05:00
const allTasks = [
2024-12-06 13:04:21 -06:00
{
status: 'Connect GitHub',
completed: false,
tier: 'Pleb',
courseId: null,
subTasks: [
{ status: 'Connect your GitHub account', completed: false },
2024-12-06 13:04:21 -06:00
]
},
2024-09-27 13:33:27 -05:00
{
status: 'PlebDevs Starter',
2024-09-27 13:33:27 -05:00
completed: false,
tier: 'Plebdev',
2025-01-03 15:08:07 -06:00
courseId: "f538f5c5-1a72-4804-8eb1-3f05cea64874",
courseNAddress: "naddr1qvzqqqr4xspzpueu32tp0jc47uzlcuxdgcw06m40ytu7ynpna2adnqty3e0vda6pqy88wumn8ghj7mn0wvhxcmmv9uq32amnwvaz7tmjv4kxz7fwv3sk6atn9e5k7tcpr9mhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv9uq3wamnwvaz7tmjv4kxz7fwdehhxarj9e3xzmny9uq36amnwvaz7tmjv4kxz7fwd46hg6tw09mkzmrvv46zucm0d5hsz9mhwden5te0wfjkccte9ec8y6tdv9kzumn9wshszynhwden5te0dehhxarjxgcjucm0d5hszynhwden5te0dehhxarjw4jjucm0d5hsz9nhwden5te0wp6hyurvv4ex2mrp0yhxxmmd9uq3wamnwvaz7tmjv4kxz7fwv3jhvueww3hk7mrn9uqzge34xvuxvdtrx5knzcfhxgkngwpsxsknsetzxyknxe3sx43k2cfkxsurwdq68epwa",
2024-09-27 13:33:27 -05:00
subTasks: [
{ status: 'Complete the course', completed: false },
2024-09-27 13:33:27 -05:00
]
},
2024-12-06 13:04:21 -06:00
{
status: 'Frontend Course',
completed: false,
tier: 'Frontend Dev',
2025-01-03 15:08:07 -06:00
courseId: 'f73c37f4-df2e-4f7d-a838-dce568c76136',
courseNAddress: "naddr1qvzqqqr4xspzpueu32tp0jc47uzlcuxdgcw06m40ytu7ynpna2adnqty3e0vda6pqy88wumn8ghj7mn0wvhxcmmv9uq32amnwvaz7tmjv4kxz7fwv3sk6atn9e5k7tcpr9mhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv9uq3wamnwvaz7tmjv4kxz7fwdehhxarj9e3xzmny9uq36amnwvaz7tmjv4kxz7fwd46hg6tw09mkzmrvv46zucm0d5hsz9mhwden5te0wfjkccte9ec8y6tdv9kzumn9wshszynhwden5te0dehhxarjxgcjucm0d5hszynhwden5te0dehhxarjw4jjucm0d5hsz9nhwden5te0wp6hyurvv4ex2mrp0yhxxmmd9uq3wamnwvaz7tmjv4kxz7fwv3jhvueww3hk7mrn9uqzge3hxd3nxdmxxskkge3jv5knge3hvskkzwpn8qkkgcm9x5mrscehxccnxdsc53n8w",
subTasks: [
{ status: 'Complete the course', completed: false },
{ status: 'Submit your project repository', completed: false },
]
},
2024-12-06 13:04:21 -06:00
{
status: 'Backend Course',
completed: false,
tier: 'Backend Dev',
2024-11-26 13:51:08 -06:00
courseId: 'f6825391-831c-44da-904a-9ac3d149b7be',
2025-01-03 15:08:07 -06:00
courseNAddress: "naddr1qvzqqqr4xspzpueu32tp0jc47uzlcuxdgcw06m40ytu7ynpna2adnqty3e0vda6pqy88wumn8ghj7mn0wvhxcmmv9uq32amnwvaz7tmjv4kxz7fwv3sk6atn9e5k7tcpr9mhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv9uq3wamnwvaz7tmjv4kxz7fwdehhxarj9e3xzmny9uq36amnwvaz7tmjv4kxz7fwd46hg6tw09mkzmrvv46zucm0d5hsz9mhwden5te0wfjkccte9ec8y6tdv9kzumn9wshszynhwden5te0dehhxarjxgcjucm0d5hszynhwden5te0dehhxarjw4jjucm0d5hsz9nhwden5te0wp6hyurvv4ex2mrp0yhxxmmd9uq3wamnwvaz7tmjv4kxz7fwv3jhvueww3hk7mrn9uqzge3k8qer2veexyknsve3vvkngdryvyknjvp5vyknjctrxdjrzdpevgmkyegqyn0ns",
subTasks: [
2024-12-06 13:04:21 -06:00
{ status: 'Complete the course', completed: false },
{ status: 'Submit your project repository', completed: false },
]
},
2024-09-27 13:33:27 -05:00
];
2024-09-20 12:22:42 -05:00
const UserProgress = () => {
const [progress, setProgress] = useState(0);
const [currentTier, setCurrentTier] = useState(null);
const [expandedItems, setExpandedItems] = useState({});
const [completedCourses, setCompletedCourses] = useState([]);
const [tasks, setTasks] = useState([]);
const [isLoading, setIsLoading] = useState(true);
2024-12-09 12:43:33 -06:00
const router = useRouter();
const { data: session, update } = useSession();
useBadge();
useEffect(() => {
if (session?.user) {
setIsLoading(true);
const user = session.user;
2024-09-27 13:33:27 -05:00
const ids = user?.userCourses?.map(course => course?.completed ? course.courseId : null).filter(id => id !== null);
if (ids && ids.length > 0) {
2024-11-26 13:57:21 -06:00
setCompletedCourses(ids);
generateTasks(ids);
calculateProgress(ids);
calculateCurrentTier(ids);
2024-09-27 13:33:27 -05:00
} else {
generateTasks([]);
2024-11-26 13:57:21 -06:00
calculateProgress([]);
calculateCurrentTier([]);
2024-09-27 13:33:27 -05:00
}
setIsLoading(false);
}
}, [session]);
const generateTasks = (completedCourseIds) => {
const updatedTasks = allTasks.map(task => {
if (task.status === 'Connect GitHub') {
return {
...task,
2024-12-09 12:43:33 -06:00
completed: session?.account?.provider === 'github' ? true : false,
subTasks: task.subTasks.map(subTask => ({
...subTask,
2024-12-09 12:43:33 -06:00
completed: session?.account?.provider === 'github' ? true : false
}))
};
}
const userCourse = session?.user?.userCourses?.find(uc => uc.courseId === task.courseId);
const courseCompleted = completedCourseIds.includes(task.courseId);
const repoSubmitted = userCourse?.submittedRepoLink ? true : false;
return {
...task,
completed: courseCompleted && (task.courseId === null || repoSubmitted),
subTasks: task.subTasks.map(subTask => ({
...subTask,
completed: subTask.status.includes('Complete')
? courseCompleted
: subTask.status.includes('repository')
? repoSubmitted
: false
}))
};
});
setTasks(updatedTasks);
};
2024-09-20 12:22:42 -05:00
2024-11-26 13:57:21 -06:00
const calculateProgress = (completedCourseIds) => {
let progressValue = 0;
2024-12-09 12:43:33 -06:00
if (session?.account?.provider === 'github') {
progressValue += 25;
}
2024-12-06 13:04:21 -06:00
2024-11-26 13:57:21 -06:00
const remainingTasks = allTasks.slice(1);
remainingTasks.forEach(task => {
if (completedCourseIds.includes(task.courseId)) {
progressValue += 25;
}
});
2024-09-20 12:22:42 -05:00
2024-11-26 13:57:21 -06:00
setProgress(progressValue);
2024-09-20 12:22:42 -05:00
};
2024-11-26 13:57:21 -06:00
const calculateCurrentTier = (completedCourseIds) => {
let tier = null;
2024-12-06 13:04:21 -06:00
2024-11-26 13:57:21 -06:00
if (completedCourseIds.includes("f6825391-831c-44da-904a-9ac3d149b7be")) {
tier = 'Backend Dev';
} else if (completedCourseIds.includes("f73c37f4-df2e-4f7d-a838-dce568c76136")) {
tier = 'Frontend Dev';
} else if (completedCourseIds.includes("f6daa88a-53d6-4901-8dbd-d2203a05b7ab")) {
tier = 'Plebdev';
2024-12-09 12:43:33 -06:00
} else if (session?.account?.provider === 'github') {
tier = 'Pleb';
2024-11-26 13:57:21 -06:00
}
2024-12-06 13:04:21 -06:00
2024-11-26 13:57:21 -06:00
setCurrentTier(tier);
};
2024-09-20 12:22:42 -05:00
const handleAccordionChange = (index, isExpanded) => {
setExpandedItems(prev => ({
...prev,
[index]: isExpanded
}));
};
const handleGitHubLink = async () => {
2024-12-09 12:43:33 -06:00
try {
// If user is already signed in, we'll link the accounts
if (session?.user) {
const result = await signIn("github", {
redirect: false,
// Pass existing user data for linking
userId: session?.user?.id,
pubkey: session?.user?.pubkey,
privkey: session?.user?.privkey || null
2024-12-09 12:43:33 -06:00
});
if (result?.ok) {
// Wait for session update
await new Promise(resolve => setTimeout(resolve, 1000));
const updatedSession = await getSession();
if (updatedSession?.account?.provider === 'github') {
router.push('/profile'); // Accounts linked successfully
2024-12-09 12:43:33 -06:00
}
}
} else {
// Normal GitHub sign in
await signIn("github");
}
} catch (error) {
console.error("GitHub sign in error:", error);
}
};
2024-09-20 12:22:42 -05:00
return (
<div className="bg-gray-800 rounded-lg p-4 pb-0 m-2 mb-0 w-full border border-gray-700 shadow-md max-lap:mx-0">
<div className="flex flex-row justify-between items-center">
<h1 className="text-3xl font-bold text-white mb-2">Dev Journey</h1>
<MoreInfo
tooltip="Learn about the Dev Journey"
modalTitle="Dev Journey"
modalBody="This is an optional Dev Journey that will walk you through the primary course materials and help you learn how to code, gain the required experience to Build Bitcoin/Lightning/Nostr Apps, and set you up to go through the rest of the free workshops and other content on the platform."
className="text-2xl text-gray-200"
/>
</div>
<p className="text-gray-400 mb-4">Track your progress through the courses, showcase your GitHub contributions, submit projects, and earn badges!</p>
2024-09-20 12:22:42 -05:00
<div className="flex justify-between items-center mb-2">
<span className="text-gray-300">Progress</span>
<span className="text-gray-300">{progress}%</span>
</div>
<ProgressBar value={progress} className="h-2 mb-6" pt={{
label: {
className: 'hidden'
}
}} />
2024-09-20 12:22:42 -05:00
<div className="mb-6">
<span className="text-white text-lg font-semibold">Current Tier: </span>
{currentTier ? (
<span className="bg-green-500 text-white px-3 py-1 rounded-full">
{currentTier}
</span>
) : (
<span className="bg-gray-700 text-gray-400 px-3 py-1 rounded-full text-sm">
Not Started
</span>
)}
2024-09-20 12:22:42 -05:00
</div>
2024-12-17 17:41:14 -06:00
<div className="flex max-sidebar:flex-col gap-6 mb-6">
<div className="w-1/2 max-sidebar:w-full">
<ul className="space-y-6 pt-2">
{tasks.map((task, index) => (
<li key={index}>
<Accordion
activeIndex={expandedItems[index] ? 0 : null}
onTabChange={(e) => handleAccordionChange(index, e.index === 0)}
>
<AccordionTab
header={
<div className="flex items-center justify-between w-full">
<div className="flex items-center">
{task.completed ? (
<div className="w-6 h-6 bg-green-500 rounded-full flex items-center justify-center mr-3">
<i className="pi pi-check text-white text-lg"></i>
</div>
) : (
<div className="w-6 h-6 bg-gray-700 rounded-full flex items-center justify-center mr-3">
<i className="pi pi-info-circle text-white text-lg"></i>
</div>
)}
<span className={`text-lg ${task.completed ? 'text-white' : 'text-gray-400'}`}>{task.status}</span>
</div>
<span className="bg-blue-500 text-white text-sm px-2 py-1 rounded-full w-24 text-center">
{task.tier}
</span>
</div>
}
>
{task.status === 'Connect GitHub' && !task.completed && (
<div className="mb-4">
<GenericButton
label="Connect GitHub"
icon="pi pi-github"
onClick={handleGitHubLink}
className="w-fit bg-[#24292e] hover:bg-[#2f363d] border border-[#f8f8ff] text-[#f8f8ff] font-semibold"
rounded
/>
</div>
)}
{task.subTasks && (
<ul className="space-y-2">
{task.subTasks.map((subTask, subIndex) => (
<li key={subIndex}>
<div className="flex items-center pl-[28px]">
{subTask.completed ? (
<div className="w-4 h-4 bg-green-500 rounded-full flex items-center justify-center mr-3">
<i className="pi pi-check text-white text-sm"></i>
</div>
) : (
<div className="w-4 h-4 bg-gray-700 rounded-full flex items-center justify-center mr-3">
<i className="pi pi-info-circle text-white text-sm"></i>
</div>
)}
<span className={`${subTask.completed ? 'text-white' : 'text-gray-400'}`}>
{subTask.status}
</span>
{subTask.status === 'Connect your GitHub account' && (
<MoreInfo
tooltip="GitHub Connection Info"
modalTitle="Connect GitHub Account"
modalBody="Connect your GitHub account to track your progress and submit projects. This allows us to verify your project submissions and track your development journey."
className="ml-2 text-sm text-gray-200"
/>
)}
</div>
{subTask.status.includes('repository') && !subTask.completed && (
<RepoSelector
courseId={task.courseId}
onSubmit={() => {
const updatedTasks = tasks.map(t =>
t.courseId === task.courseId
? {
...t,
subTasks: t.subTasks.map(st =>
st.status === subTask.status
? { ...st, completed: true }
: st
)
}
: t
);
setTasks(updatedTasks);
router.push('/profile');
}}
/>
)}
</li>
))}
</ul>
)}
{task.courseNAddress && (
<div className="mt-2 flex justify-end">
<GenericButton
icon="pi pi-external-link"
label="View Course"
onClick={() => router.push(`/course/${task.courseNAddress}`)}
outlined
size="small"
/>
</div>
)}
</AccordionTab>
</Accordion>
</li>
))}
</ul>
</div>
2024-12-17 17:41:14 -06:00
<div className="w-1/2 max-sidebar:w-full">
{isLoading ? (
<div className="h-[400px] bg-gray-800 rounded-3xl flex items-center justify-center">
<i className="pi pi-spin pi-spinner text-4xl text-gray-600"></i>
</div>
) : (
<UserProgressFlow tasks={tasks} />
)}
</div>
</div>
2024-09-20 12:22:42 -05:00
</div>
);
};
export default UserProgress;