Starting to restructure user profile around new dev journey

This commit is contained in:
austinkelsay 2024-12-11 18:04:22 -06:00
parent 6c0e09292b
commit c04a42eeb7
No known key found for this signature in database
GPG Key ID: 44CB4EC6D9F2FA02
9 changed files with 351 additions and 275 deletions

View File

@ -157,71 +157,76 @@ const ActivityContributionChart = ({ session }) => {
const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
return ( return (
<div className="mx-auto py-2 px-8 max-w-[1000px] bg-gray-800 rounded-lg"> <div className="w-full mx-2 bg-gray-800 rounded-lg border border-gray-700 shadow-md">
<div className="flex justify-between items-center mb-3"> <div className="flex flex-row justify-between items-center p-4">
<h4 className="text-base font-semibold text-gray-200"> <h1 className="text-2xl font-bold text-gray-200">Activity</h1>
{totalActivities} learning activities in the last year <i className="pi pi-question-circle text-2xl cursor-pointer text-gray-200"
</h4> data-pr-tooltip="Total number of learning activities on the platform" />
<i className="pi pi-question-circle text-lg cursor-pointer text-gray-400 hover:text-gray-200" <Tooltip target=".pi-question-circle" position="left" />
data-pr-tooltip="Total number of learning activities on the platform" />
<Tooltip target=".pi-question-circle" position="top" />
</div> </div>
<div className="flex"> <div className="max-w-[910px] p-4">
{/* Days of week labels */} <div className="flex justify-between items-center mb-3">
<div className="flex flex-col gap-[3px] text-[11px] text-gray-400 pr-3"> <h4 className="text-base font-semibold text-gray-200">
{weekDays.map((day, index) => ( {totalActivities} learning activities in the last year
<div key={day} className="h-[13px] leading-[13px]"> </h4>
{index % 2 === 0 && day} </div>
<div className="flex">
{/* Days of week labels */}
<div className="flex flex-col gap-[3px] text-[11px] text-gray-400 pr-3">
{weekDays.map((day, index) => (
<div key={day} className="h-[14px] leading-[14px]">
{index % 2 === 0 && day}
</div>
))}
</div>
<div className="flex flex-col">
{/* Calendar grid */}
<div className="flex gap-[3px]">
{calendar[0].map((_, weekIndex) => (
<div key={weekIndex} className="flex flex-col gap-[3px]">
{calendar.map((row, dayIndex) => (
row[weekIndex] && (
<div
key={`${weekIndex}-${dayIndex}`}
className={`w-[14px] h-[14px] ${getColor(row[weekIndex].count)} rounded-[2px] cursor-pointer transition-colors duration-100`}
title={`${row[weekIndex].date.toDateString()}: ${
row[weekIndex].count > 0
? `${row[weekIndex].count} activit${row[weekIndex].count !== 1 ? 'ies' : 'y'}`
: 'No activities'
}`}
></div>
)
))}
</div>
))}
</div> </div>
))} {/* Month labels */}
<div className="flex text-[11px] text-gray-400 h-[20px] mt-1">
{getMonthLabels().map((month, index) => (
<div
key={index}
className="absolute"
style={{ marginLeft: `${month.index * 15}px` }}
>
{month.name}
</div>
))}
</div>
</div>
</div> </div>
<div className="flex flex-col"> {/* Legend */}
{/* Calendar grid */} <div className="text-[11px] text-gray-400 flex items-center justify-end">
<span className="mr-2">Less</span>
<div className="flex gap-[3px]"> <div className="flex gap-[3px]">
{calendar[0].map((_, weekIndex) => ( <div className="w-[14px] h-[14px] bg-gray-100 rounded-[2px]"></div>
<div key={weekIndex} className="flex flex-col gap-[3px]"> <div className="w-[14px] h-[14px] bg-green-300 rounded-[2px]"></div>
{calendar.map((row, dayIndex) => ( <div className="w-[14px] h-[14px] bg-green-400 rounded-[2px]"></div>
row[weekIndex] && ( <div className="w-[14px] h-[14px] bg-green-600 rounded-[2px]"></div>
<div <div className="w-[14px] h-[14px] bg-green-700 rounded-[2px]"></div>
key={`${weekIndex}-${dayIndex}`}
className={`w-[13px] h-[13px] ${getColor(row[weekIndex].count)} rounded-[2px] cursor-pointer transition-colors duration-100`}
title={`${row[weekIndex].date.toDateString()}: ${
row[weekIndex].count > 0
? `${row[weekIndex].count} activit${row[weekIndex].count !== 1 ? 'ies' : 'y'}`
: 'No activities'
}`}
></div>
)
))}
</div>
))}
</div>
{/* Month labels */}
<div className="flex text-[11px] text-gray-400 h-[20px] mt-1">
{getMonthLabels().map((month, index) => (
<div
key={index}
className="absolute"
style={{ marginLeft: `${month.index * 15}px` }}
>
{month.name}
</div>
))}
</div> </div>
<span className="ml-2">More</span>
</div> </div>
</div> </div>
{/* Legend */}
<div className="text-[11px] text-gray-400 flex items-center justify-end">
<span className="mr-2">Less</span>
<div className="flex gap-[3px]">
<div className="w-[13px] h-[13px] bg-gray-100 rounded-[2px]"></div>
<div className="w-[13px] h-[13px] bg-green-300 rounded-[2px]"></div>
<div className="w-[13px] h-[13px] bg-green-400 rounded-[2px]"></div>
<div className="w-[13px] h-[13px] bg-green-600 rounded-[2px]"></div>
<div className="w-[13px] h-[13px] bg-green-700 rounded-[2px]"></div>
</div>
<span className="ml-2">More</span>
</div>
</div> </div>
); );
}; };

View File

@ -181,73 +181,77 @@ const CombinedContributionChart = ({ session }) => {
}, [calendar]); }, [calendar]);
return ( return (
<div className="mx-auto py-2 px-8 max-w-[1000px] bg-gray-800 rounded-lg"> <div className="w-full mx-2 bg-gray-800 rounded-lg border border-gray-700 shadow-md">
{(isLoading || isFetching) && <p>Loading contribution data... ({totalContributions} total contributions / activities fetched)</p>} <div className="flex flex-row justify-between items-center p-4">
{!isLoading && !isFetching && <h1 className="text-2xl font-bold text-gray-200">Activity</h1>
<div className="flex justify-between items-center mb-3"> <i className="pi pi-question-circle text-2xl cursor-pointer text-gray-200"
<h4 className="text-base font-semibold text-gray-200"> data-pr-tooltip="Combined total of GitHub commits and learning activities (starting/completing courses and lessons)" />
{totalContributions} total contributions / activities in the last year <Tooltip target=".pi-question-circle" position="left" />
</h4>
<i className="pi pi-question-circle text-lg cursor-pointer text-gray-400 hover:text-gray-200"
data-pr-tooltip="Combined total of GitHub commits and learning activities (starting/completing courses and lessons)" />
<Tooltip target=".pi-question-circle" position="top" />
</div> </div>
} <div className="max-w-[910px] p-4">
<div className="flex"> {(isLoading || isFetching) && <h4 className="text-base font-semibold text-gray-200 mb-3">Loading contribution data... ({totalContributions} total contributions / activities fetched)</h4>}
{/* Days of week labels */} {!isLoading && !isFetching &&
<div className="flex flex-col gap-[3px] text-[11px] text-gray-400 pr-3"> <div className="flex justify-between items-center mb-3">
{weekDays.map((day, index) => ( <h4 className="text-base font-semibold text-gray-200">
<div key={day} className="h-[13px] leading-[13px]"> {totalContributions} total contributions / activities in the last year
{index % 2 === 0 && day} </h4>
</div> </div>
))} }
</div> <div className="flex">
<div className="flex flex-col"> {/* Days of week labels */}
{/* Calendar grid */} <div className="flex flex-col gap-[3px] text-[11px] text-gray-400 pr-3">
<div className="flex gap-[3px]"> {weekDays.map((day, index) => (
{calendar[0].map((_, weekIndex) => ( <div key={day} className="h-[13px] leading-[13px]">
<div key={weekIndex} className="flex flex-col gap-[3px]"> {index % 2 === 0 && day}
{calendar.map((row, dayIndex) => ( </div>
row[weekIndex] && ( ))}
<div </div>
key={`${weekIndex}-${dayIndex}`} <div className="flex flex-col">
className={`w-[13px] h-[13px] ${getColor(row[weekIndex].count)} rounded-[2px] cursor-pointer transition-colors duration-100`} {/* Calendar grid */}
title={`${row[weekIndex].date.toDateString()}: ${ <div className="flex gap-[3px]">
[ {calendar[0].map((_, weekIndex) => (
<div key={weekIndex} className="flex flex-col gap-[3px]">
{calendar.map((row, dayIndex) => (
row[weekIndex] && (
<div
key={`${weekIndex}-${dayIndex}`}
className={`w-[14px] h-[14px] ${getColor(row[weekIndex].count)} rounded-[2px] cursor-pointer transition-colors duration-100`}
title={`${row[weekIndex].date.toDateString()}: ${[
row[weekIndex].githubCount > 0 ? `${row[weekIndex].githubCount} contribution${row[weekIndex].githubCount !== 1 ? 's' : ''}` : '', row[weekIndex].githubCount > 0 ? `${row[weekIndex].githubCount} contribution${row[weekIndex].githubCount !== 1 ? 's' : ''}` : '',
row[weekIndex].activityCount > 0 ? `${row[weekIndex].activityCount} activit${row[weekIndex].activityCount !== 1 ? 'ies' : 'y'}` : '' row[weekIndex].activityCount > 0 ? `${row[weekIndex].activityCount} activit${row[weekIndex].activityCount !== 1 ? 'ies' : 'y'}` : ''
].filter(Boolean).join(' & ') || 'No contributions or activities' ].filter(Boolean).join(' & ') || 'No contributions or activities'
}`} }`}
></div> ></div>
) )
))} ))}
</div> </div>
))} ))}
</div> </div>
{/* Month labels moved to bottom */} {/* Month labels moved to bottom */}
<div className="flex text-[11px] text-gray-400 h-[20px] mt-1"> <div className="flex text-[11px] text-gray-400 h-[20px] mt-1">
{getMonthLabels().map((month, index) => ( {getMonthLabels().map((month, index) => (
<div <div
key={index} key={index}
className="absolute" className="absolute"
style={{ marginLeft: `${month.index * 15}px` }} style={{ marginLeft: `${month.index * 15}px` }}
> >
{month.name} {month.name}
</div> </div>
))} ))}
</div>
</div> </div>
</div> </div>
</div> <div className="text-[11px] text-gray-400 flex items-center justify-end">
<div className="text-[11px] text-gray-400 flex items-center justify-end"> <span className="mr-2">Less</span>
<span className="mr-2">Less</span> <div className="flex gap-[3px]">
<div className="flex gap-[3px]"> <div className="w-[14px] h-[14px] bg-gray-100 rounded-[2px]"></div>
<div className="w-[13px] h-[13px] bg-gray-100 rounded-[2px]"></div> <div className="w-[14px] h-[14px] bg-green-300 rounded-[2px]"></div>
<div className="w-[13px] h-[13px] bg-green-300 rounded-[2px]"></div> <div className="w-[14px] h-[14px] bg-green-400 rounded-[2px]"></div>
<div className="w-[13px] h-[13px] bg-green-400 rounded-[2px]"></div> <div className="w-[14px] h-[14px] bg-green-600 rounded-[2px]"></div>
<div className="w-[13px] h-[13px] bg-green-600 rounded-[2px]"></div> <div className="w-[14px] h-[14px] bg-green-700 rounded-[2px]"></div>
<div className="w-[13px] h-[13px] bg-green-700 rounded-[2px]"></div> </div>
<span className="ml-2">More</span>
</div> </div>
<span className="ml-2">More</span>
</div> </div>
</div> </div>
); );

View File

@ -129,7 +129,7 @@ const UserProgressTable = ({ session, ndk, windowWidth }) => {
emptyMessage="No Courses or Milestones completed" emptyMessage="No Courses or Milestones completed"
value={prepareProgressData()} value={prepareProgressData()}
header={header} header={header}
style={{ maxWidth: windowWidth < 768 ? "100%" : "90%", margin: "0 auto" }} style={{ margin: 8, width: "100%", borderRadius: "8px", border: "1px solid #333", boxShadow: "0 0 10px 0 rgba(0, 0, 0, 0.1)" }}
pt={{ pt={{
wrapper: { wrapper: {
className: "rounded-b-lg shadow-md" className: "rounded-b-lg shadow-md"

View File

@ -53,7 +53,7 @@ const UserPurchaseTable = ({ session, windowWidth }) => {
emptyMessage="No purchases" emptyMessage="No purchases"
value={session.user?.purchased} value={session.user?.purchased}
header={purchasesHeader} header={purchasesHeader}
style={{ maxWidth: windowWidth < 768 ? "100%" : "90%", margin: "0 auto", marginTop: "2rem" }} style={{ margin: 8, width: "100%", borderRadius: "8px", border: "1px solid #333", boxShadow: "0 0 10px 0 rgba(0, 0, 0, 0.1)" }}
pt={{ pt={{
wrapper: { wrapper: {
className: "rounded-b-lg shadow-md" className: "rounded-b-lg shadow-md"

View File

@ -37,24 +37,27 @@ const UserProfile = () => {
<h1 className="text-3xl font-bold mb-6">Profile</h1> <h1 className="text-3xl font-bold mb-6">Profile</h1>
) )
} }
<div className="w-full flex flex-col justify-center mx-auto"> <div className="w-full flex flex-row">
<UserProfileCard user={user} /> <UserProfileCard user={user} />
{account && account?.provider === "github" ? ( <div className="w-full flex flex-col justify-center mx-auto">
<CombinedContributionChart session={session} />
) : ( {account && account?.provider === "github" ? (
<ActivityContributionChart session={session} /> <CombinedContributionChart session={session} />
)} ) : (
<UserProgress /> <ActivityContributionChart session={session} />
)}
<UserProgress />
<UserProgressTable
session={session}
ndk={ndk}
windowWidth={windowWidth}
/>
<UserPurchaseTable
session={session}
windowWidth={windowWidth}
/>
</div>
</div> </div>
<UserProgressTable
session={session}
ndk={ndk}
windowWidth={windowWidth}
/>
<UserPurchaseTable
session={session}
windowWidth={windowWidth}
/>
</div> </div>
) )
); );

View File

@ -41,8 +41,8 @@ const UserProfileCard = ({ user }) => {
]; ];
return ( return (
<> <div className="lg:w-1/4 bg-gray-800 rounded-lg p-4 border border-gray-700 shadow-md">
<div className="relative flex w-full items-center justify-center"> <div className="flex flex-row gap-4">
<Image <Image
alt="user's avatar" alt="user's avatar"
src={returnImageProxy(user.avatar, user?.pubkey || "")} src={returnImageProxy(user.avatar, user?.pubkey || "")}
@ -50,41 +50,72 @@ const UserProfileCard = ({ user }) => {
height={100} height={100}
className="rounded-full my-4" className="rounded-full my-4"
/> />
<div className="absolute top-8 right-80 max-tab:right-20 max-mob:left-0">
<i <div className="flex flex-col gap-2 pt-4 w-full relative">
className="pi pi-ellipsis-h text-2xl cursor-pointer" <div className="absolute top-0 right-0">
onClick={(e) => menu.current.toggle(e)} <i
/> className="pi pi-ellipsis-h text-2xl cursor-pointer"
<Menu onClick={(e) => menu.current.toggle(e)}
model={menuItems} />
popup <Menu
ref={menu} model={menuItems}
id="profile-options-menu" popup
/> ref={menu}
id="profile-options-menu"
/>
</div>
<h3 className="self-start">
{user.username || user?.name || user?.email || "Anon"}
</h3>
{
user?.pubkey && (
<div className="flex flex-row gap-2">
<p className="truncate">
{nip19.npubEncode(user.pubkey).slice(0, 12)}...
</p>
<Tooltip target=".pubkey-tooltip" content={"this is your account pubkey"} />
<i className="pi pi-question-circle pubkey-tooltip text-xs cursor-pointer" />
</div>
)
}
{user?.createdAt && (
<p className="truncate">
Joined: {new Date(user.createdAt).toLocaleDateString()}
</p>
)}
</div> </div>
</div> </div>
<div className="flex flex-col gap-2">
{user?.lightningAddress ? (
<h1 className="text-center text-2xl my-2"> <h4 className="my-2 bg-gray-900 rounded-lg p-4">
{user.username || user?.name || user?.email || "Anon"} <span className="font-bold">Lightning Address:</span> {user.lightningAddress.name}@plebdevs.com <i className="pi pi-copy cursor-pointer hover:text-gray-400" onClick={() => copyToClipboard(user.lightningAddress.name + "@plebdevs.com")} />
</h1> </h4>
{user.pubkey && ( ) : (
<h2 className="text-center text-xl my-2 truncate max-tab:px-4 max-mob:px-4"> <div className="flex flex-row justify-between my-2 bg-gray-900 rounded-lg p-4">
<Tooltip target=".pubkey-tooltip" content={"this is your nostr npub"} /> <h4 >
{nip19.npubEncode(user.pubkey)} <i className="pi pi-question-circle text-xl pubkey-tooltip" /> <span className="font-bold">Lightning Address:</span> None
</h2> </h4>
)} {/* todo: add tooltip */}
{user?.lightningAddress && ( <Tooltip target=".lightning-address-tooltip" content={"this is your account lightning address"} />
<h3 className="w-fit mx-auto text-center text-xl my-2 bg-gray-800 rounded-lg p-4"> <i className="pi pi-question-circle lightning-address-tooltip text-xs cursor-pointer" />
<span className="font-bold">Lightning Address:</span> {user.lightningAddress.name}@plebdevs.com <i className="pi pi-copy cursor-pointer hover:text-gray-400" onClick={() => copyToClipboard(user.lightningAddress.name + "@plebdevs.com")} /> </div>
</h3> )}
)} {user?.nip05 ? (
{user?.nip05 && ( <h4 className="my-2 bg-gray-900 rounded-lg p-4">
<h3 className="w-fit mx-auto text-center text-xl my-2 bg-gray-800 rounded-lg p-4"> <span className="font-bold">NIP-05:</span> {user.nip05.name}@plebdevs.com <i className="pi pi-copy cursor-pointer hover:text-gray-400" onClick={() => copyToClipboard(user.nip05.name + "@plebdevs.com")} />
<span className="font-bold">NIP-05:</span> {user.nip05.name}@plebdevs.com <i className="pi pi-copy cursor-pointer hover:text-gray-400" onClick={() => copyToClipboard(user.nip05.name + "@plebdevs.com")} /> </h4>
</h3> ) : (
)} <div className="flex flex-row justify-between my-2 bg-gray-900 rounded-lg p-4">
</> <h4>
<span className="font-bold">NIP-05:</span> None
</h4>
{/* todo: add tooltip */}
<Tooltip target=".nip05-tooltip" content={"this is your account nip05"} />
<i className="pi pi-question-circle nip05-tooltip text-xs cursor-pointer" />
</div>
)}
</div>
</div>
); );
}; };

View File

@ -6,6 +6,7 @@ import { useRouter } from 'next/router';
import GenericButton from '@/components/buttons/GenericButton'; import GenericButton from '@/components/buttons/GenericButton';
import UserBadges from '@/components/profile/UserBadges'; import UserBadges from '@/components/profile/UserBadges';
import UserProgressFlow from './UserProgressFlow'; import UserProgressFlow from './UserProgressFlow';
import { Tooltip } from 'primereact/tooltip';
const allTasks = [ const allTasks = [
{ {
@ -56,12 +57,14 @@ const UserProgress = () => {
const [completedCourses, setCompletedCourses] = useState([]); const [completedCourses, setCompletedCourses] = useState([]);
const [tasks, setTasks] = useState([]); const [tasks, setTasks] = useState([]);
const [showBadges, setShowBadges] = useState(false); const [showBadges, setShowBadges] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const router = useRouter(); const router = useRouter();
const { data: session, update } = useSession(); const { data: session, update } = useSession();
useEffect(() => { useEffect(() => {
if (session?.user) { if (session?.user) {
setIsLoading(true);
const user = session.user; const user = session.user;
const ids = user?.userCourses?.map(course => course?.completed ? course.courseId : null).filter(id => id !== null); const ids = user?.userCourses?.map(course => course?.completed ? course.courseId : null).filter(id => id !== null);
if (ids && ids.length > 0) { if (ids && ids.length > 0) {
@ -74,6 +77,7 @@ const UserProgress = () => {
calculateProgress([]); calculateProgress([]);
calculateCurrentTier([]); calculateCurrentTier([]);
} }
setIsLoading(false);
} }
}, [session]); }, [session]);
@ -173,8 +177,13 @@ const UserProgress = () => {
}; };
return ( return (
<div className="bg-gray-800 rounded-3xl p-6 w-[940px] max-mob:w-full max-tab:w-full mx-auto my-8"> <div className="bg-gray-800 rounded-lg p-4 m-2 w-full border border-gray-700 shadow-md">
<h1 className="text-3xl font-bold text-white mb-2">Dev Journey</h1> <div className="flex flex-row justify-between items-center">
<h1 className="text-3xl font-bold text-white mb-2">Dev Journey</h1>
<i className="pi pi-question-circle text-2xl cursor-pointer text-gray-200"
data-pr-tooltip="Track your progress from Pleb to Plebdev" />
<Tooltip target=".pi-question-circle" position="left" />
</div>
<p className="text-gray-400 mb-4">Track your progress from Pleb to Plebdev</p> <p className="text-gray-400 mb-4">Track your progress from Pleb to Plebdev</p>
<div className="flex justify-between items-center mb-2"> <div className="flex justify-between items-center mb-2">
@ -200,88 +209,98 @@ const UserProgress = () => {
)} )}
</div> </div>
<div className="mb-6"> <div className="flex gap-6 mb-6">
<UserProgressFlow tasks={tasks} /> <div className="w-1/2">
</div> <ul className="space-y-6 pt-2">
{tasks.map((task, index) => (
<ul className="space-y-4 mb-6"> <li key={index}>
{tasks.map((task, index) => ( <Accordion
<li key={index}> activeIndex={expandedItems[index] ? 0 : null}
<Accordion onTabChange={(e) => handleAccordionChange(index, e.index === 0)}
activeIndex={expandedItems[index] ? 0 : null} >
onTabChange={(e) => handleAccordionChange(index, e.index === 0)} <AccordionTab
> header={
<AccordionTab <div className="flex items-center justify-between w-full">
header={ <div className="flex items-center">
<div className="flex items-center justify-between w-full"> {task.completed ? (
<div className="flex items-center"> <div className="w-6 h-6 bg-green-500 rounded-full flex items-center justify-center mr-3">
{task.completed ? ( <i className="pi pi-check text-white text-lg"></i>
<div className="w-6 h-6 bg-green-500 rounded-full flex items-center justify-center mr-3"> </div>
<i className="pi pi-check text-white text-lg"></i> ) : (
<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> </div>
) : ( <span className="bg-blue-500 text-white text-sm px-2 py-1 rounded-full w-24 text-center">
<div className="w-6 h-6 bg-gray-700 rounded-full flex items-center justify-center mr-3"> {task.tier}
<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} className="flex items-center">
{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> </span>
</li> </div>
))} }
</ul> >
)} {task.status === 'Connect GitHub' && !task.completed && (
{task.courseId && ( <div className="mb-4">
<div className="mt-2 flex justify-end"> <GenericButton
<GenericButton label="Connect GitHub"
icon="pi pi-external-link" icon="pi pi-github"
onClick={() => router.push(`/courses/${task.courseId}`)} onClick={handleGitHubLink}
tooltip="View Course" className="w-fit bg-[#24292e] hover:bg-[#2f363d] border border-[#f8f8ff] text-[#f8f8ff] font-semibold"
tooltipOptions={{ rounded
position: "top" />
}} </div>
outlined )}
size="small" {task.subTasks && (
/> <ul className="space-y-2">
</div> {task.subTasks.map((subTask, subIndex) => (
)} <li key={subIndex} className="flex items-center pl-[28px]">
</AccordionTab> {subTask.completed ? (
</Accordion> <div className="w-4 h-4 bg-green-500 rounded-full flex items-center justify-center mr-3">
</li> <i className="pi pi-check text-white text-sm"></i>
))} </div>
</ul> ) : (
<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>
</li>
))}
</ul>
)}
{task.courseId && (
<div className="mt-2 flex justify-end">
<GenericButton
icon="pi pi-external-link"
onClick={() => router.push(`/courses/${task.courseId}`)}
tooltip="View Course"
tooltipOptions={{
position: "top"
}}
outlined
size="small"
/>
</div>
)}
</AccordionTab>
</Accordion>
</li>
))}
</ul>
</div>
<div className="w-1/2">
{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>
<button <button
className="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-full font-semibold" className="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-full font-semibold"

View File

@ -3,19 +3,20 @@ import ReactFlow, {
Background, Background,
Handle, Handle,
Position, Position,
Controls
} from 'reactflow'; } from 'reactflow';
import 'reactflow/dist/style.css'; import 'reactflow/dist/style.css';
const CustomNode = ({ data }) => ( const CustomNode = ({ data }) => (
<div className={`px-4 py-2 rounded-lg shadow-md w-48 text-center transition-all duration-300 ${ <div className={`px-4 py-2 rounded-lg shadow-md w-48 text-center transition-all duration-300 ${
data.completed data.completed
? 'bg-green-500 text-white border-2 border-green-400' ? 'bg-green-500 text-white border-2 border-green-400 bg-opacity-50'
: 'bg-gray-700 text-gray-300 border-2 border-gray-600' : 'bg-gray-700 text-gray-300 border-2 border-gray-600 bg-opacity-50'
}`}> }`}>
<Handle type="target" position={Position.Top} /> <Handle type="target" position={Position.Top} />
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
{data.completed ? ( {data.completed ? (
<div className="w-5 h-5 bg-green-400 rounded-full flex items-center justify-center"> <div className="w-5 h-5 bg-green-500 rounded-full flex items-center justify-center">
<i className="pi pi-check text-white text-sm"></i> <i className="pi pi-check text-white text-sm"></i>
</div> </div>
) : ( ) : (
@ -37,6 +38,13 @@ const nodeTypes = {
}; };
const UserProgressFlow = ({ tasks }) => { const UserProgressFlow = ({ tasks }) => {
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
const nodes = [ const nodes = [
{ {
id: '1', id: '1',
@ -113,6 +121,8 @@ const UserProgressFlow = ({ tasks }) => {
}, },
]; ];
if (!mounted) return <div style={{ height: 400 }} className="bg-gray-800 rounded-3xl" />;
return ( return (
<div style={{ height: 400 }} className="bg-gray-800 rounded-3xl"> <div style={{ height: 400 }} className="bg-gray-800 rounded-3xl">
<ReactFlow <ReactFlow
@ -120,16 +130,20 @@ const UserProgressFlow = ({ tasks }) => {
edges={edges} edges={edges}
nodeTypes={nodeTypes} nodeTypes={nodeTypes}
fitView fitView
preventScrolling
zoomOnScroll={false}
panOnScroll={false}
nodesDraggable={false} nodesDraggable={false}
nodesConnectable={false} nodesConnectable={false}
elementsSelectable={false} elementsSelectable={false}
minZoom={0.5} panOnDrag={false}
maxZoom={2} zoomOnScroll={false}
panOnScroll={false}
selectNodesOnDrag={false}
preventScrolling
minZoom={1}
maxZoom={1}
defaultViewport={{ x: 0, y: 0, zoom: 1 }}
> >
<Background color="#4a5568" gap={16} /> <Background color="#4a5568" gap={16} />
{/* <Controls position="top-right" /> */}
</ReactFlow> </ReactFlow>
</div> </div>
); );

View File

@ -231,7 +231,8 @@ export const authOptions = {
purchased: fullUser.purchased, purchased: fullUser.purchased,
nip05: fullUser.nip05, nip05: fullUser.nip05,
lightningAddress: fullUser.lightningAddress, lightningAddress: fullUser.lightningAddress,
githubUsername: token.githubUsername githubUsername: token.githubUsername,
createdAt: fullUser.createdAt
}; };
// Add GitHub account info to session if it exists // Add GitHub account info to session if it exists
@ -249,7 +250,6 @@ export const authOptions = {
return session; return session;
}, },
async jwt({ token, user, account, profile, session }) { async jwt({ token, user, account, profile, session }) {
console.log("JWT", token, user, account, profile, session);
// If we are linking a github account to an existing email or anon account (we have privkey) // If we are linking a github account to an existing email or anon account (we have privkey)
if (account?.provider === "github" && user?.id && user?.pubkey && user?.privkey) { if (account?.provider === "github" && user?.id && user?.pubkey && user?.privkey) {
try { try {