feat: update public profile

feat/public-profile
Arik Chakma 4 weeks ago
parent 4183871a75
commit c81fba3535
  1. 3
      src/components/Dashboard/DashboardPage.tsx
  2. 86
      src/components/Dashboard/PersonalDashboard.tsx
  3. 33
      src/components/UpdateProfile/UpdatePublicProfileForm.tsx
  4. 18
      src/components/UserPublicProfile/UserPublicProfileHeader.tsx

@ -54,13 +54,14 @@ export function DashboardPage(props: DashboardPageProps) {
return (
<div className="min-h-screen bg-gray-50 pb-20 pt-8">
<div className="container">
<div className="mb-6 sm:mb-8 flex flex-wrap items-center gap-1.5">
<div className="mb-6 flex flex-wrap items-center gap-1.5 sm:mb-8">
<DashboardTab
label="Personal"
isActive={!selectedTeamId}
onClick={() => setSelectedTeamId(undefined)}
avatar={userAvatar}
/>
{isLoading && (
<>
<DashboardTabSkeleton />

@ -14,6 +14,9 @@ import { CheckEmoji } from '../ReactIcons/CheckEmoji.tsx';
import { ConstructionEmoji } from '../ReactIcons/ConstructionEmoji.tsx';
import { BookEmoji } from '../ReactIcons/BookEmoji.tsx';
import { DashboardAiRoadmaps } from './DashboardAiRoadmaps.tsx';
import type { AllowedProfileVisibility } from '../../api/user.ts';
import { PencilIcon, type LucideIcon } from 'lucide-react';
import { cn } from '../../lib/classname.ts';
type UserDashboardResponse = {
name: string;
@ -21,6 +24,7 @@ type UserDashboardResponse = {
avatar: string;
headline: string;
username: string;
profileVisibility: AllowedProfileVisibility;
progresses: UserProgress[];
projects: ProjectStatusDocument[];
aiRoadmaps: {
@ -222,18 +226,20 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
return 0;
});
const { username } = personalDashboardDetails || {};
return (
<section>
{isLoading ? (
<div className="h-7 w-1/4 animate-pulse rounded-lg bg-gray-200"></div>
) : (
<div className="flex items-start sm:items-center justify-between flex-col sm:flex-row gap-1">
<div className="flex flex-col items-start justify-between gap-1 sm:flex-row sm:items-center">
<h2 className="text-lg font-medium">
Hi {name}, good {getCurrentPeriod()}!
</h2>
<a
href="/home"
className="text-xs font-medium bg-gray-200 hover:bg-gray-300 px-2.5 py-1 rounded-full text-gray-700 hover:text-black"
className="rounded-full bg-gray-200 px-2.5 py-1 text-xs font-medium text-gray-700 hover:bg-gray-300 hover:text-black"
>
Visit Homepage
</a>
@ -253,8 +259,16 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
<DashboardCard
imgUrl={avatarLink}
title={name!}
description="Setup your profile"
href="/account/update-profile"
description={
username ? 'View your profile' : 'Setup your profile'
}
href={username ? `/u/${username}` : '/account/update-profile'}
{...(username && {
externalLinkIcon: PencilIcon,
externalLinkHref: '/account/update-profile',
externalLinkText: 'Edit',
})}
className={username ? 'border-dashed' : ''}
/>
<DashboardCard
@ -312,33 +326,61 @@ type DashboardCardProps = {
title: string;
description: string;
href: string;
externalLinkIcon?: LucideIcon;
externalLinkText?: string;
externalLinkHref?: string;
className?: string;
};
function DashboardCard(props: DashboardCardProps) {
const { icon: Icon, imgUrl, title, description, href } = props;
const {
icon: Icon,
imgUrl,
title,
description,
href,
externalLinkHref,
externalLinkIcon: ExternalLinkIcon,
externalLinkText,
className,
} = props;
return (
<a
href={href}
className="flex flex-col overflow-hidden rounded-lg border border-gray-300 bg-white hover:border-gray-400 hover:bg-gray-50"
>
{Icon && (
<div className="px-4 pb-3 pt-4">
<Icon className="size-6" />
</div>
<div
className={cn(
'relative overflow-hidden rounded-lg border border-gray-300 bg-white hover:border-gray-400 hover:bg-gray-50',
className,
)}
>
<a href={href} className="flex flex-col">
{Icon && (
<div className="px-4 pb-3 pt-4">
<Icon className="size-6" />
</div>
)}
{imgUrl && (
<div className="px-4 pb-1.5 pt-3.5">
<img src={imgUrl} alt={title} className="size-8 rounded-full" />
</div>
)}
{imgUrl && (
<div className="px-4 pb-1.5 pt-3.5">
<img src={imgUrl} alt={title} className="size-8 rounded-full" />
<div className="flex grow flex-col justify-center gap-0.5 p-4">
<h3 className="truncate font-medium text-black">{title}</h3>
<p className="text-xs text-black">{description}</p>
</div>
</a>
{externalLinkHref && (
<a
href={externalLinkHref}
className="absolute right-0 top-0 flex items-center gap-1.5 rounded-bl-md bg-gray-200 p-1 px-2 text-sm text-gray-600 hover:bg-gray-300 hover:text-black"
>
{ExternalLinkIcon && <ExternalLinkIcon className="size-3" />}
{externalLinkText}
</a>
)}
<div className="flex grow flex-col justify-center gap-0.5 p-4">
<h3 className="truncate font-medium text-black">{title}</h3>
<p className="text-xs text-black">{description}</p>
</div>
</a>
</div>
);
}

@ -71,6 +71,7 @@ export function UpdatePublicProfileForm() {
const [profileRoadmaps, setProfileRoadmaps] = useState<RoadmapType[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isProfileUpdated, setIsProfileUpdated] = useState(false);
const { isCopied, copyText } = useCopyText();
@ -109,6 +110,7 @@ export function UpdatePublicProfileForm() {
await loadProfileSettings();
toast.success('Profile updated successfully');
setIsProfileUpdated(true);
};
const loadProfileSettings = async () => {
@ -593,6 +595,37 @@ export function UpdatePublicProfileForm() {
>
{isLoading ? 'Please wait..' : 'Save Profile'}
</button>
{isProfileUpdated && publicProfileUrl && (
<div className="flex items-center gap-4">
<button
type="button"
className={cn(
'flex items-center justify-center gap-2 text-gray-500 underline underline-offset-2 hover:text-black hover:no-underline',
isCopied
? 'text-green-500 hover:text-green-600'
: 'text-gray-500',
)}
onClick={() => {
copyText(`${window.location.origin}${publicProfileUrl}`);
}}
>
{isCopied ? (
<CheckCircle className="size-4" />
) : (
<Copy className="size-4" />
)}
Copy Profile URL
</button>
<a
className="flex items-center justify-center gap-2 text-gray-500 underline underline-offset-2 hover:text-black hover:no-underline"
href={publicProfileUrl}
target="_blank"
>
<ArrowUpRight className="size-4" />
View Profile
</a>
</div>
)}
</form>
</div>
);

@ -3,6 +3,7 @@ import {
Globe,
LinkedinIcon,
Mail,
Pencil,
Twitter,
} from 'lucide-react';
import type { GetPublicProfileResponse } from '../../api/user';
@ -15,11 +16,12 @@ type UserPublicProfileHeaderProps = {
export function UserPublicProfileHeader(props: UserPublicProfileHeaderProps) {
const { userDetails } = props;
const { name, links, publicConfig, avatar, email } = userDetails;
const { name, links, publicConfig, avatar, email, isOwnProfile } =
userDetails;
const { headline, isAvailableForHire, isEmailVisible } = publicConfig!;
return (
<div className="container flex items-center gap-6 rounded-xl border bg-white p-8">
<div className="container relative flex items-center gap-6 rounded-xl border bg-white p-8">
<img
src={
avatar
@ -27,7 +29,7 @@ export function UserPublicProfileHeader(props: UserPublicProfileHeaderProps) {
: '/images/default-avatar.png'
}
alt={name}
className="h-32 w-32 object-cover rounded-full"
className="h-32 w-32 rounded-full object-cover"
/>
<div>
@ -51,6 +53,16 @@ export function UserPublicProfileHeader(props: UserPublicProfileHeaderProps) {
{isEmailVisible && <UserLink href={`mailto:${email}`} icon={Mail} />}
</div>
</div>
{isOwnProfile && (
<a
href="/account/update-profile"
className="absolute right-4 top-4 flex items-center gap-1.5 text-sm text-gray-500 hover:text-black"
>
<Pencil className="h-3 w-3 stroke-2" />
Edit Profile
</a>
)}
</div>
);
}

Loading…
Cancel
Save