fix: user public page

pull/5494/head
Arik Chakma 8 months ago
parent 8bf0b51065
commit 5e5f7427f9
  1. 30
      src/api/user.ts
  2. 74
      src/components/UserPublicAccount/UserPublicAccountPage.tsx
  3. 0
      src/components/UserPublicAccount/UserPublicActivityHeatmap.tsx
  4. 10
      src/components/UserPublicAccount/UserPublicDetails.tsx
  5. 84
      src/components/UserPublicAccount/UserPublicProgressStats.tsx
  6. 10
      src/pages/u/[username]/index.astro

@ -38,6 +38,29 @@ export type UserActivityCount = {
totalActivityCount: number; totalActivityCount: number;
}; };
type ProgressResponse = {
updatedAt: string;
title: string;
id: string;
learning: number;
skipped: number;
done: number;
total: number;
isCustomResource?: boolean;
roadmapSlug?: string;
};
export type UserResourceProgressStats = {
done: {
total: number;
};
learning: {
total: number;
roadmaps: ProgressResponse[];
bestPractices: ProgressResponse[];
};
};
export type GetUserByUsernameResponse = Omit< export type GetUserByUsernameResponse = Omit<
UserDocument, UserDocument,
| 'password' | 'password'
@ -45,9 +68,10 @@ export type GetUserByUsernameResponse = Omit<
| 'resetPasswordCode' | 'resetPasswordCode'
| 'resetPasswordCodeAt' | 'resetPasswordCodeAt'
| 'email' | 'email'
> & { > &
activity: UserActivityCount; UserResourceProgressStats & {
}; activity: UserActivityCount;
};
export function userApi(context: APIContext) { export function userApi(context: APIContext) {
return { return {

@ -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>
);
}

@ -1,11 +1,11 @@
import { Github, Globe, LinkedinIcon, Twitter } from 'lucide-react'; import { Github, Globe, LinkedinIcon, Twitter } from 'lucide-react';
import type { GetUserByUsernameResponse } from '../../api/user'; import type { GetUserByUsernameResponse } from '../../api/user';
type UserDetailsProps = { type UserPublicDetailsProps = {
userDetails: GetUserByUsernameResponse; userDetails: GetUserByUsernameResponse;
}; };
export function UserDetails(props: UserDetailsProps) { export function UserPublicDetails(props: UserPublicDetailsProps) {
const { userDetails } = props; const { userDetails } = props;
const { name, username, links } = userDetails; const { name, username, links } = userDetails;
@ -20,7 +20,11 @@ export function UserDetails(props: UserDetailsProps) {
<div> <div>
<h1 className="text-2xl font-bold">{name}</h1> <h1 className="text-2xl font-bold">{name}</h1>
<p className="text-gray-500">@{username}</p> <p className="mt-1 text-sm text-gray-500">
I'm a Frontend developer interested in filmmaking, content creation,
vlogging, and backend, currently living in Dhaka, Bangladesh. Right
now I'm writing html at @roadmapsh. Let's grab a coffee.
</p>
</div> </div>
</div> </div>

@ -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> &bull;
</>
)}
{learningCount > 0 && (
<>
<span>{learningCount} in progress</span> &bull;
</>
)}
{skippedCount > 0 && (
<>
<span>{skippedCount} skipped</span> &bull;
</>
)}
<span>{totalCount} total</span>
</span>
</div>
</div>
);
}

@ -1,6 +1,5 @@
--- ---
import { UserActivityHeatmap } from '../../../components/Account/UserActivityHeatmap'; import { UserPublicAccountPage } from '../../../components/UserPublicAccount/UserPublicAccountPage';
import { UserDetails } from '../../../components/Account/UserDetails';
import { userApi } from '../../../api/user'; import { userApi } from '../../../api/user';
import AccountLayout from '../../../layouts/AccountLayout.astro'; import AccountLayout from '../../../layouts/AccountLayout.astro';
@ -19,10 +18,5 @@ if (error || !userDetails) {
--- ---
<AccountLayout title={userDetails?.name}> <AccountLayout title={userDetails?.name}>
<section class='container mt-5'> <UserPublicAccountPage {...userDetails} client:load />
<UserDetails userDetails={userDetails!} client:load />
<div class='mt-6'>
<UserActivityHeatmap activity={userDetails?.activity!} client:load />
</div>
</section>
</AccountLayout> </AccountLayout>

Loading…
Cancel
Save