mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-04-19 19:01:19 +00:00
Starting to restructure user profile around new dev journey
This commit is contained in:
parent
6c0e09292b
commit
c04a42eeb7
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
)
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user