parent
8bf0b51065
commit
5e5f7427f9
6 changed files with 194 additions and 14 deletions
@ -0,0 +1,74 @@ |
||||
import type { GetUserByUsernameResponse } from '../../api/user'; |
||||
import { UserActivityHeatmap } from './UserPublicActivityHeatmap'; |
||||
import { UserPublicDetails } from './UserPublicDetails'; |
||||
import { UserPublicProgressStats } from './UserPublicProgressStats'; |
||||
|
||||
type UserPublicAccountPageProps = GetUserByUsernameResponse; |
||||
|
||||
export function UserPublicAccountPage(props: UserPublicAccountPageProps) { |
||||
const { activity, learning } = props; |
||||
|
||||
const learningRoadmaps = learning?.roadmaps || []; |
||||
const learningBestPractices = learning?.bestPractices || []; |
||||
|
||||
return ( |
||||
<section className="container mt-5 pb-10"> |
||||
<UserPublicDetails userDetails={props!} /> |
||||
<div className="mt-6"> |
||||
<UserActivityHeatmap activity={activity!} /> |
||||
</div> |
||||
|
||||
{(learningRoadmaps.length > 0 || learningBestPractices.length > 0) && ( |
||||
<> |
||||
<h2 className="mt-6 text-xl font-bold">Learning Progress</h2> |
||||
<div className="mt-4 flex flex-col gap-3"> |
||||
{learningRoadmaps |
||||
.sort((a, b) => { |
||||
const updatedAtA = new Date(a.updatedAt); |
||||
const updatedAtB = new Date(b.updatedAt); |
||||
|
||||
return updatedAtB.getTime() - updatedAtA.getTime(); |
||||
}) |
||||
.map((roadmap) => ( |
||||
<UserPublicProgressStats |
||||
key={roadmap.id} |
||||
roadmapSlug={roadmap.roadmapSlug} |
||||
isCustomResource={roadmap.isCustomResource} |
||||
doneCount={roadmap.done || 0} |
||||
learningCount={roadmap.learning || 0} |
||||
totalCount={roadmap.total || 0} |
||||
skippedCount={roadmap.skipped || 0} |
||||
resourceId={roadmap.id} |
||||
resourceType={'roadmap'} |
||||
updatedAt={roadmap.updatedAt} |
||||
title={roadmap.title} |
||||
/> |
||||
))} |
||||
|
||||
{learningBestPractices |
||||
.sort((a, b) => { |
||||
const updatedAtA = new Date(a.updatedAt); |
||||
const updatedAtB = new Date(b.updatedAt); |
||||
|
||||
return updatedAtB.getTime() - updatedAtA.getTime(); |
||||
}) |
||||
.map((bestPractice) => ( |
||||
<UserPublicProgressStats |
||||
isCustomResource={bestPractice.isCustomResource} |
||||
key={bestPractice.id} |
||||
doneCount={bestPractice.done || 0} |
||||
totalCount={bestPractice.total || 0} |
||||
learningCount={bestPractice.learning || 0} |
||||
resourceId={bestPractice.id} |
||||
skippedCount={bestPractice.skipped || 0} |
||||
resourceType={'best-practice'} |
||||
title={bestPractice.title} |
||||
updatedAt={bestPractice.updatedAt} |
||||
/> |
||||
))} |
||||
</div> |
||||
</> |
||||
)} |
||||
</section> |
||||
); |
||||
} |
@ -0,0 +1,84 @@ |
||||
import { getRelativeTimeString } from '../../lib/date'; |
||||
|
||||
type UserPublicProgressStats = { |
||||
resourceType: 'roadmap' | 'best-practice'; |
||||
resourceId: string; |
||||
title: string; |
||||
updatedAt: string; |
||||
totalCount: number; |
||||
doneCount: number; |
||||
learningCount: number; |
||||
skippedCount: number; |
||||
showClearButton?: boolean; |
||||
isCustomResource?: boolean; |
||||
roadmapSlug?: string; |
||||
}; |
||||
|
||||
export function UserPublicProgressStats(props: UserPublicProgressStats) { |
||||
const { |
||||
updatedAt, |
||||
resourceType, |
||||
resourceId, |
||||
title, |
||||
totalCount, |
||||
learningCount, |
||||
doneCount, |
||||
skippedCount, |
||||
roadmapSlug, |
||||
isCustomResource = false, |
||||
} = props; |
||||
|
||||
let url = |
||||
resourceType === 'roadmap' |
||||
? `/${resourceId}` |
||||
: `/best-practices/${resourceId}`; |
||||
|
||||
if (isCustomResource) { |
||||
url = `/r/${roadmapSlug}`; |
||||
} |
||||
|
||||
const totalMarked = doneCount + skippedCount; |
||||
const progressPercentage = Math.round((totalMarked / totalCount) * 100); |
||||
|
||||
return ( |
||||
<div> |
||||
<a |
||||
href={url} |
||||
className="group relative flex cursor-pointer items-center rounded-t-md border p-3 text-gray-600 hover:border-gray-300 hover:text-black" |
||||
> |
||||
<span |
||||
className={`absolute left-0 top-0 block h-full cursor-pointer rounded-tl-md bg-black/5 group-hover:bg-black/10`} |
||||
style={{ |
||||
width: `${progressPercentage}%`, |
||||
}} |
||||
></span> |
||||
<span className="relative flex-1 cursor-pointer truncate"> |
||||
{title} |
||||
</span> |
||||
<span className="ml-1 cursor-pointer text-sm text-gray-400"> |
||||
{getRelativeTimeString(updatedAt)} |
||||
</span> |
||||
</a> |
||||
<div className="sm:space-between flex flex-row items-start rounded-b-md border border-t-0 px-2 py-2 text-xs text-gray-500"> |
||||
<span className="hidden flex-1 gap-1 sm:flex"> |
||||
{doneCount > 0 && ( |
||||
<> |
||||
<span>{doneCount} done</span> • |
||||
</> |
||||
)} |
||||
{learningCount > 0 && ( |
||||
<> |
||||
<span>{learningCount} in progress</span> • |
||||
</> |
||||
)} |
||||
{skippedCount > 0 && ( |
||||
<> |
||||
<span>{skippedCount} skipped</span> • |
||||
</> |
||||
)} |
||||
<span>{totalCount} total</span> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
Loading…
Reference in new issue