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'];
return (
<div className="mx-auto py-2 px-8 max-w-[1000px] bg-gray-800 rounded-lg">
<div className="flex justify-between items-center mb-3">
<h4 className="text-base font-semibold text-gray-200">
{totalActivities} learning activities in the last year
</h4>
<i className="pi pi-question-circle text-lg cursor-pointer text-gray-400 hover:text-gray-200"
data-pr-tooltip="Total number of learning activities on the platform" />
<Tooltip target=".pi-question-circle" position="top" />
<div className="w-full mx-2 bg-gray-800 rounded-lg border border-gray-700 shadow-md">
<div className="flex flex-row justify-between items-center p-4">
<h1 className="text-2xl font-bold text-gray-200">Activity</h1>
<i className="pi pi-question-circle text-2xl cursor-pointer text-gray-200"
data-pr-tooltip="Total number of learning activities on the platform" />
<Tooltip target=".pi-question-circle" position="left" />
</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-[13px] leading-[13px]">
{index % 2 === 0 && day}
<div className="max-w-[910px] p-4">
<div className="flex justify-between items-center mb-3">
<h4 className="text-base font-semibold text-gray-200">
{totalActivities} learning activities in the last year
</h4>
</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>
))}
{/* 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 className="flex flex-col">
{/* Calendar grid */}
{/* Legend */}
<div className="text-[11px] text-gray-400 flex items-center justify-end">
<span className="mr-2">Less</span>
<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-[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 className="w-[14px] h-[14px] bg-gray-100 rounded-[2px]"></div>
<div className="w-[14px] h-[14px] bg-green-300 rounded-[2px]"></div>
<div className="w-[14px] h-[14px] bg-green-400 rounded-[2px]"></div>
<div className="w-[14px] h-[14px] bg-green-600 rounded-[2px]"></div>
<div className="w-[14px] h-[14px] bg-green-700 rounded-[2px]"></div>
</div>
<span className="ml-2">More</span>
</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>
);
};

View File

@ -9,10 +9,10 @@ const CombinedContributionChart = ({ session }) => {
const prepareProgressData = useCallback(() => {
if (!session?.user?.userCourses) return {};
const activityData = {};
const allActivities = []; // Array to store all activities for logging
// Process course activities
session.user.userCourses.forEach(courseProgress => {
if (courseProgress.started) {
@ -74,15 +74,15 @@ const CombinedContributionChart = ({ session }) => {
const handleNewCommit = useCallback(({ contributionData, totalCommits }) => {
const activityData = prepareProgressData();
console.log("GitHub Contribution Data:", contributionData);
// Create a new object with GitHub commits
const combinedData = { ...contributionData };
// Add activities to the combined data
Object.entries(activityData).forEach(([date, count]) => {
combinedData[date] = (combinedData[date] || 0) + count;
});
console.log("Combined Data:", combinedData);
setContributionData(combinedData);
setTotalContributions(totalCommits + Object.values(activityData).reduce((a, b) => a + b, 0));
@ -95,12 +95,12 @@ const CombinedContributionChart = ({ session }) => {
if (data && !isLoading) {
const activityData = prepareProgressData();
const combinedData = { ...data.contributionData };
// Add activities to the combined data
Object.entries(activityData).forEach(([date, count]) => {
combinedData[date] = (combinedData[date] || 0) + count;
});
setContributionData(combinedData);
setTotalContributions(data.totalCommits + Object.values(activityData).reduce((a, b) => a + b, 0));
}
@ -117,15 +117,15 @@ const CombinedContributionChart = ({ session }) => {
const generateCalendar = useCallback(() => {
const today = new Date();
today.setHours(23, 59, 59, 999);
// Calculate the start date (52 weeks + remaining days to today)
const oneYearAgo = new Date(today);
oneYearAgo.setDate(today.getDate() - 364); // 52 weeks * 7 days = 364 days
// Start from the first Sunday before or on oneYearAgo
const startDate = new Date(oneYearAgo);
startDate.setDate(startDate.getDate() - startDate.getDay());
const calendar = [];
// Create 7 rows for days of the week (Sunday to Saturday)
for (let i = 0; i < 7; i++) {
@ -140,14 +140,14 @@ const CombinedContributionChart = ({ session }) => {
const githubCount = data?.contributionData[dateString] || 0;
const activityCount = (contributionData[dateString] || 0) - (data?.contributionData[dateString] || 0);
const totalCount = githubCount + activityCount;
calendar[weekDay].push({
date: new Date(currentDate),
count: totalCount,
githubCount,
activityCount
});
currentDate.setDate(currentDate.getDate() + 1);
}
@ -181,73 +181,77 @@ const CombinedContributionChart = ({ session }) => {
}, [calendar]);
return (
<div className="mx-auto py-2 px-8 max-w-[1000px] bg-gray-800 rounded-lg">
{(isLoading || isFetching) && <p>Loading contribution data... ({totalContributions} total contributions / activities fetched)</p>}
{!isLoading && !isFetching &&
<div className="flex justify-between items-center mb-3">
<h4 className="text-base font-semibold text-gray-200">
{totalContributions} total contributions / activities in the last year
</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 className="w-full mx-2 bg-gray-800 rounded-lg border border-gray-700 shadow-md">
<div className="flex flex-row justify-between items-center p-4">
<h1 className="text-2xl font-bold text-gray-200">Activity</h1>
<i className="pi pi-question-circle text-2xl cursor-pointer 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="left" />
</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-[13px] leading-[13px]">
{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-[13px] h-[13px] ${getColor(row[weekIndex].count)} rounded-[2px] cursor-pointer transition-colors duration-100`}
title={`${row[weekIndex].date.toDateString()}: ${
[
<div className="max-w-[910px] p-4">
{(isLoading || isFetching) && <h4 className="text-base font-semibold text-gray-200 mb-3">Loading contribution data... ({totalContributions} total contributions / activities fetched)</h4>}
{!isLoading && !isFetching &&
<div className="flex justify-between items-center mb-3">
<h4 className="text-base font-semibold text-gray-200">
{totalContributions} total contributions / activities in the last year
</h4>
</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-[13px] leading-[13px]">
{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].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'}` : ''
].filter(Boolean).join(' & ') || 'No contributions or activities'
}`}
></div>
)
))}
</div>
))}
</div>
{/* Month labels moved to bottom */}
<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>
{/* Month labels moved to bottom */}
<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="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 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-[14px] h-[14px] bg-gray-100 rounded-[2px]"></div>
<div className="w-[14px] h-[14px] bg-green-300 rounded-[2px]"></div>
<div className="w-[14px] h-[14px] bg-green-400 rounded-[2px]"></div>
<div className="w-[14px] h-[14px] bg-green-600 rounded-[2px]"></div>
<div className="w-[14px] h-[14px] bg-green-700 rounded-[2px]"></div>
</div>
<span className="ml-2">More</span>
</div>
<span className="ml-2">More</span>
</div>
</div>
);

View File

@ -129,7 +129,7 @@ const UserProgressTable = ({ session, ndk, windowWidth }) => {
emptyMessage="No Courses or Milestones completed"
value={prepareProgressData()}
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={{
wrapper: {
className: "rounded-b-lg shadow-md"

View File

@ -53,7 +53,7 @@ const UserPurchaseTable = ({ session, windowWidth }) => {
emptyMessage="No purchases"
value={session.user?.purchased}
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={{
wrapper: {
className: "rounded-b-lg shadow-md"

View File

@ -37,24 +37,27 @@ const UserProfile = () => {
<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} />
{account && account?.provider === "github" ? (
<CombinedContributionChart session={session} />
) : (
<ActivityContributionChart session={session} />
)}
<UserProgress />
<div className="w-full flex flex-col justify-center mx-auto">
{account && account?.provider === "github" ? (
<CombinedContributionChart session={session} />
) : (
<ActivityContributionChart session={session} />
)}
<UserProgress />
<UserProgressTable
session={session}
ndk={ndk}
windowWidth={windowWidth}
/>
<UserPurchaseTable
session={session}
windowWidth={windowWidth}
/>
</div>
</div>
<UserProgressTable
session={session}
ndk={ndk}
windowWidth={windowWidth}
/>
<UserPurchaseTable
session={session}
windowWidth={windowWidth}
/>
</div>
)
);

View File

@ -41,8 +41,8 @@ const UserProfileCard = ({ user }) => {
];
return (
<>
<div className="relative flex w-full items-center justify-center">
<div className="lg:w-1/4 bg-gray-800 rounded-lg p-4 border border-gray-700 shadow-md">
<div className="flex flex-row gap-4">
<Image
alt="user's avatar"
src={returnImageProxy(user.avatar, user?.pubkey || "")}
@ -50,41 +50,72 @@ const UserProfileCard = ({ user }) => {
height={100}
className="rounded-full my-4"
/>
<div className="absolute top-8 right-80 max-tab:right-20 max-mob:left-0">
<i
className="pi pi-ellipsis-h text-2xl cursor-pointer"
onClick={(e) => menu.current.toggle(e)}
/>
<Menu
model={menuItems}
popup
ref={menu}
id="profile-options-menu"
/>
<div className="flex flex-col gap-2 pt-4 w-full relative">
<div className="absolute top-0 right-0">
<i
className="pi pi-ellipsis-h text-2xl cursor-pointer"
onClick={(e) => menu.current.toggle(e)}
/>
<Menu
model={menuItems}
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>
<h1 className="text-center text-2xl my-2">
{user.username || user?.name || user?.email || "Anon"}
</h1>
{user.pubkey && (
<h2 className="text-center text-xl my-2 truncate max-tab:px-4 max-mob:px-4">
<Tooltip target=".pubkey-tooltip" content={"this is your nostr npub"} />
{nip19.npubEncode(user.pubkey)} <i className="pi pi-question-circle text-xl pubkey-tooltip" />
</h2>
)}
{user?.lightningAddress && (
<h3 className="w-fit mx-auto text-center text-xl my-2 bg-gray-800 rounded-lg p-4">
<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")} />
</h3>
)}
{user?.nip05 && (
<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")} />
</h3>
)}
</>
<div className="flex flex-col gap-2">
{user?.lightningAddress ? (
<h4 className="my-2 bg-gray-900 rounded-lg p-4">
<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")} />
</h4>
) : (
<div className="flex flex-row justify-between my-2 bg-gray-900 rounded-lg p-4">
<h4 >
<span className="font-bold">Lightning Address:</span> None
</h4>
{/* todo: add tooltip */}
<Tooltip target=".lightning-address-tooltip" content={"this is your account lightning address"} />
<i className="pi pi-question-circle lightning-address-tooltip text-xs cursor-pointer" />
</div>
)}
{user?.nip05 ? (
<h4 className="my-2 bg-gray-900 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")} />
</h4>
) : (
<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 UserBadges from '@/components/profile/UserBadges';
import UserProgressFlow from './UserProgressFlow';
import { Tooltip } from 'primereact/tooltip';
const allTasks = [
{
@ -56,12 +57,14 @@ const UserProgress = () => {
const [completedCourses, setCompletedCourses] = useState([]);
const [tasks, setTasks] = useState([]);
const [showBadges, setShowBadges] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const router = useRouter();
const { data: session, update } = useSession();
useEffect(() => {
if (session?.user) {
setIsLoading(true);
const user = session.user;
const ids = user?.userCourses?.map(course => course?.completed ? course.courseId : null).filter(id => id !== null);
if (ids && ids.length > 0) {
@ -74,6 +77,7 @@ const UserProgress = () => {
calculateProgress([]);
calculateCurrentTier([]);
}
setIsLoading(false);
}
}, [session]);
@ -173,8 +177,13 @@ const UserProgress = () => {
};
return (
<div className="bg-gray-800 rounded-3xl p-6 w-[940px] max-mob:w-full max-tab:w-full mx-auto my-8">
<h1 className="text-3xl font-bold text-white mb-2">Dev Journey</h1>
<div className="bg-gray-800 rounded-lg p-4 m-2 w-full border border-gray-700 shadow-md">
<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>
<div className="flex justify-between items-center mb-2">
@ -200,88 +209,98 @@ const UserProgress = () => {
)}
</div>
<div className="mb-6">
<UserProgressFlow tasks={tasks} />
</div>
<ul className="space-y-4 mb-6">
{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 className="flex gap-6 mb-6">
<div className="w-1/2">
<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>
) : (
<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} 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 className="bg-blue-500 text-white text-sm px-2 py-1 rounded-full w-24 text-center">
{task.tier}
</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>
}
>
{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 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>
</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
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,
Handle,
Position,
Controls
} from 'reactflow';
import 'reactflow/dist/style.css';
const CustomNode = ({ data }) => (
<div className={`px-4 py-2 rounded-lg shadow-md w-48 text-center transition-all duration-300 ${
data.completed
? 'bg-green-500 text-white border-2 border-green-400'
: 'bg-gray-700 text-gray-300 border-2 border-gray-600'
? '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-opacity-50'
}`}>
<Handle type="target" position={Position.Top} />
<div className="flex items-center justify-center gap-2">
{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>
</div>
) : (
@ -37,6 +38,13 @@ const nodeTypes = {
};
const UserProgressFlow = ({ tasks }) => {
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
const nodes = [
{
id: '1',
@ -113,6 +121,8 @@ const UserProgressFlow = ({ tasks }) => {
},
];
if (!mounted) return <div style={{ height: 400 }} className="bg-gray-800 rounded-3xl" />;
return (
<div style={{ height: 400 }} className="bg-gray-800 rounded-3xl">
<ReactFlow
@ -120,16 +130,20 @@ const UserProgressFlow = ({ tasks }) => {
edges={edges}
nodeTypes={nodeTypes}
fitView
preventScrolling
zoomOnScroll={false}
panOnScroll={false}
nodesDraggable={false}
nodesConnectable={false}
elementsSelectable={false}
minZoom={0.5}
maxZoom={2}
panOnDrag={false}
zoomOnScroll={false}
panOnScroll={false}
selectNodesOnDrag={false}
preventScrolling
minZoom={1}
maxZoom={1}
defaultViewport={{ x: 0, y: 0, zoom: 1 }}
>
<Background color="#4a5568" gap={16} />
{/* <Controls position="top-right" /> */}
</ReactFlow>
</div>
);

View File

@ -231,7 +231,8 @@ export const authOptions = {
purchased: fullUser.purchased,
nip05: fullUser.nip05,
lightningAddress: fullUser.lightningAddress,
githubUsername: token.githubUsername
githubUsername: token.githubUsername,
createdAt: fullUser.createdAt
};
// Add GitHub account info to session if it exists
@ -249,7 +250,6 @@ export const authOptions = {
return 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 (account?.provider === "github" && user?.id && user?.pubkey && user?.privkey) {
try {