Add public profile page

pull/5494/head
Kamran Ahmed 8 months ago
parent 634af33756
commit 51b8e58d7c
  1. 8
      src/api/user.ts
  2. 7
      src/components/UserPublicProfile/UserPublicActivityHeatmap.tsx
  3. 5
      src/components/UserPublicProfile/UserPublicProfilePage.tsx
  4. 75
      src/components/UserPublicProfile/UserPublicProgresses.tsx
  5. 7
      src/lib/date.ts
  6. 13
      src/pages/u/[username]/index.astro

@ -54,10 +54,10 @@ export interface UserDocument {
roadmapVisibility: AllowedRoadmapVisibility; roadmapVisibility: AllowedRoadmapVisibility;
customRoadmapVisibility: AllowedCustomRoadmapVisibility; customRoadmapVisibility: AllowedCustomRoadmapVisibility;
}; };
resetPasswordCodeAt: Date; resetPasswordCodeAt: string;
verifiedAt: Date; verifiedAt: string;
createdAt: Date; createdAt: string;
updatedAt: Date; updatedAt: string;
} }
export type UserActivityCount = { export type UserActivityCount = {

@ -2,12 +2,13 @@ import CalendarHeatmap from 'react-calendar-heatmap';
import { Tooltip as ReactTooltip } from 'react-tooltip'; import { Tooltip as ReactTooltip } from 'react-tooltip';
import 'react-calendar-heatmap/dist/styles.css'; import 'react-calendar-heatmap/dist/styles.css';
import 'react-tooltip/dist/react-tooltip.css'; import 'react-tooltip/dist/react-tooltip.css';
import { formatActivityDate } from '../../lib/date'; import { formatActivityDate, formatMonthDate } from '../../lib/date';
import type { UserActivityCount } from '../../api/user'; import type { UserActivityCount } from '../../api/user';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
type UserActivityHeatmapProps = { type UserActivityHeatmapProps = {
activity: UserActivityCount; activity: UserActivityCount;
joinedAt: string;
}; };
const legends = [ const legends = [
@ -37,7 +38,9 @@ export function UserActivityHeatmap(props: UserActivityHeatmapProps) {
Progress updates over the past year Progress updates over the past year
</p> </p>
</div> </div>
<span className="text-sm text-gray-400">Member since: June 2021</span> <span className="text-sm text-gray-400">
Member since: {formatMonthDate(props.joinedAt)}
</span>
</div> </div>
<CalendarHeatmap <CalendarHeatmap
startDate={startDate} startDate={startDate}

@ -13,10 +13,11 @@ export function UserPublicProfilePage(props: UserPublicProfilePageProps) {
isOwnProfile, isOwnProfile,
profileVisibility, profileVisibility,
_id: userId, _id: userId,
createdAt,
} = props; } = props;
return ( return (
<div className="bg-gray-200/40"> <div className="bg-gray-200/40 min-h-full flex-grow pt-10 pb-36">
<div className="container flex flex-col gap-8"> <div className="container flex flex-col gap-8">
<PrivateProfileBanner <PrivateProfileBanner
isOwnProfile={isOwnProfile} isOwnProfile={isOwnProfile}
@ -25,7 +26,7 @@ export function UserPublicProfilePage(props: UserPublicProfilePageProps) {
<UserPublicProfileHeader userDetails={props!} /> <UserPublicProfileHeader userDetails={props!} />
<UserActivityHeatmap activity={activity!} /> <UserActivityHeatmap joinedAt={createdAt} activity={activity!} />
<UserPublicProgresses <UserPublicProgresses
username={username!} username={username!}
userId={userId!} userId={userId!}

@ -1,5 +1,6 @@
import type { GetPublicProfileResponse } from '../../api/user'; import type { GetPublicProfileResponse } from '../../api/user';
import { UserPublicProgressStats } from './UserPublicProgressStats'; import { UserPublicProgressStats } from './UserPublicProgressStats';
import { getPercentage } from '../../helper/number.ts';
type UserPublicProgressesProps = { type UserPublicProgressesProps = {
userId: string; userId: string;
@ -42,12 +43,20 @@ export function UserPublicProgresses(props: UserPublicProgressesProps) {
return ( return (
<div> <div>
{customRoadmapVisibility !== 'none' && customRoadmaps?.length > 0 && ( {customRoadmapVisibility !== 'none' && customRoadmaps?.length > 0 && (
<div className='mb-5'> <div className="mb-5">
<h2 className="mb-2 text-xs uppercase tracking-wide text-gray-400"> <h2 className="mb-2 text-xs uppercase tracking-wide text-gray-400">
Roadmaps made by me Roadmaps made by me
</h2> </h2>
<div className="grid grid-cols-3"> <div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2 md:grid-cols-3">
{customRoadmaps.map((roadmap, counter) => ( {customRoadmaps.map((roadmap, counter) => {
const doneCount = roadmap.done;
const skippedCount = roadmap.skipped;
const totalCount = roadmap.total;
const totalMarked = doneCount + skippedCount;
const progressPercentage = getPercentage(totalMarked, totalCount);
return (
<a <a
target="_blank" target="_blank"
href={`/r/${roadmap.roadmapSlug}`} href={`/r/${roadmap.roadmapSlug}`}
@ -56,38 +65,48 @@ export function UserPublicProgresses(props: UserPublicProgressesProps) {
> >
{roadmap.title} {roadmap.title}
</a> </a>
))} );
})}
</div> </div>
</div> </div>
)} )}
<div> {roadmapVisibility !== 'none' && roadmaps.length > 0 && (
{roadmapVisibility !== 'none' && (
<> <>
<h2 className="text-xs uppercase text-gray-400">My Skills</h2> <h2 className="mb-2 text-xs uppercase tracking-wide text-gray-400">
<ul className=""> Skills I have mastered
{roadmaps.map((roadmap, counter) => ( </h2>
<li key={roadmap.id + counter} className="bg-white"> <div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2 md:grid-cols-3">
<UserPublicProgressStats {roadmaps.map((roadmap, counter) => {
updatedAt={roadmap.updatedAt} const percentageDone = getPercentage(
title={roadmap.title} roadmap.done + roadmap.skipped,
totalCount={roadmap.total} roadmap.total,
doneCount={roadmap.done} );
learningCount={roadmap.learning}
skippedCount={roadmap.skipped} return (
resourceId={roadmap.id} <a
resourceType="roadmap" target="_blank"
roadmapSlug={roadmap.roadmapSlug} key={roadmap.id + counter}
isCustomResource={false} href={`/${roadmap.id}?s=${userId}`}
username={username!} className="relative group border-gray-300 flex items-center justify-between rounded-md border bg-white px-3 py-2 text-left text-sm transition-all hover:border-gray-400 overflow-hidden"
userId={userId} >
/> <span className="flex-grow truncate">{roadmap.title}</span>
</li> <span className="text-xs text-gray-400">
))} {parseInt(percentageDone, 10)}%
</ul> </span>
<span
className="absolute transition-colors left-0 top-0 block h-full cursor-pointer rounded-tl-md bg-black/5 group-hover:bg-black/10"
style={{
width: `${percentageDone}%`,
}}
></span>
</a>
);
})}
</div>
</> </>
)} )}
</div> </div>
</div>
); );
} }

@ -33,6 +33,13 @@ export function getRelativeTimeString(date: string): string {
return relativeTime; return relativeTime;
} }
export function formatMonthDate(date: string): string {
return new Date(date).toLocaleDateString('en-US', {
month: 'long',
year: 'numeric',
});
}
export function formatActivityDate(date: string): string { export function formatActivityDate(date: string): string {
return new Date(date).toLocaleDateString('en-US', { return new Date(date).toLocaleDateString('en-US', {
month: 'long', month: 'long',

@ -3,6 +3,8 @@ import { FrownIcon } from 'lucide-react';
import { userApi } from '../../../api/user'; import { userApi } from '../../../api/user';
import AccountLayout from '../../../layouts/AccountLayout.astro'; import AccountLayout from '../../../layouts/AccountLayout.astro';
import { UserPublicProfilePage } from '../../../components/UserPublicProfile/UserPublicProfilePage'; import { UserPublicProfilePage } from '../../../components/UserPublicProfile/UserPublicProfilePage';
import OpenSourceBanner from '../../../components/OpenSourceBanner.astro';
import Footer from '../../../components/Footer.astro';
interface Params extends Record<string, string | undefined> { interface Params extends Record<string, string | undefined> {
username: string; username: string;
@ -40,11 +42,18 @@ if (error || !userDetails) {
height='120' height='120'
/> />
</picture> </picture>
<h2 class='my-2 sm:my-3 text-2xl sm:text-4xl font-bold'>Problem loading user!</h2> <h2 class='my-2 text-2xl font-bold sm:my-3 sm:text-4xl'>
Problem loading user!
</h2>
<p class='text-lg'> <p class='text-lg'>
<span class='bg-red-600 text-white py-1 rounded-md px-2'>{errorMessage}</span> <span class='rounded-md bg-red-600 px-2 py-1 text-white'>
{errorMessage}
</span>
</p> </p>
</div> </div>
) )
} }
<OpenSourceBanner />
<Footer />
</AccountLayout> </AccountLayout>

Loading…
Cancel
Save