Skip to content

Commit 69faafc

Browse files
feat: Build Profile Overview Section with User Info, Stats, and Organizations (#289)
1 parent 8c5246c commit 69faafc

10 files changed

Lines changed: 225 additions & 105 deletions

File tree

app/profile/[username]/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use client';
2+
import ProfileOverview from '@/components/profile/ProfileOverview';
23
import { useEffect, useState } from 'react';
34

45
interface ProfilePageProps {
@@ -33,6 +34,7 @@ export default function ProfilePage({ params }: ProfilePageProps) {
3334
<div className='container mx-auto px-4 py-8'>
3435
<h1 className='text-2xl font-bold text-white'>Profile: {username}</h1>
3536
</div>
37+
<ProfileOverview username={username} />
3638
</section>
3739
);
3840
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Image from 'next/image';
2+
import { Organization } from '@/types/profile';
3+
4+
interface OrganizationCardProps {
5+
organization: Organization;
6+
}
7+
8+
export default function OrganizationCard({
9+
organization,
10+
}: OrganizationCardProps) {
11+
return (
12+
<div className='flex items-center gap-3 px-3'>
13+
<div className='relative size-[46px] overflow-hidden rounded-full'>
14+
<Image
15+
src={organization.avatarUrl}
16+
alt={`${organization.name} avatar`}
17+
layout='fill'
18+
objectFit='cover'
19+
/>
20+
</div>
21+
<p className='font-base font-normal'>{organization.name}</p>
22+
</div>
23+
);
24+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Organization } from '@/types/profile';
2+
import OrganizationCard from './OrganizationCard';
3+
4+
interface OrganizationsListProps {
5+
organizations: Organization[];
6+
}
7+
8+
export default function OrganizationsList({
9+
organizations,
10+
}: OrganizationsListProps) {
11+
return (
12+
<main className='flex flex-col gap-3'>
13+
<h5 className='text-sm font-medium text-gray-500'>ORGANIZATIONS</h5>
14+
<main className='flex flex-col gap-3'>
15+
{organizations.map(org => (
16+
<OrganizationCard key={org.name} organization={org} />
17+
))}
18+
</main>
19+
</main>
20+
);
21+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import Image from 'next/image';
2+
import Link from 'next/link';
3+
import { UserProfile } from '@/types/profile';
4+
import { BoundlessButton } from '@/components/buttons';
5+
import { BellPlus } from 'lucide-react';
6+
import { ProfileSocialLinks } from '@/lib/config';
7+
8+
interface ProfileHeaderProps {
9+
profile: UserProfile;
10+
}
11+
12+
export default function ProfileHeader({ profile }: ProfileHeaderProps) {
13+
return (
14+
<main className='flex flex-col gap-6'>
15+
<header className='flex items-end gap-4'>
16+
<div className='relative size-[150px] overflow-hidden rounded-full bg-red-500'>
17+
<Image
18+
src={profile.avatarUrl}
19+
alt={`${profile.displayName} avatar`}
20+
layout='fill'
21+
objectFit='cover'
22+
/>
23+
</div>
24+
<div className='flex flex-col gap-3 py-3'>
25+
<h3 className='text-2xl font-medium'>{profile.displayName}</h3>
26+
<p className='text-base font-normal'>@{profile.username}</p>
27+
</div>
28+
</header>
29+
<p className='text-base font-normal'>{profile.bio}</p>
30+
<div className='flex items-center space-x-4'>
31+
{Object.entries(ProfileSocialLinks).map(([name, href], index) => (
32+
<div key={name} className='flex items-center'>
33+
<Link
34+
href={href}
35+
className='rounded transition-opacity hover:opacity-80 focus:ring-2 focus:ring-white/50 focus:outline-none'
36+
target='_blank'
37+
rel='noopener noreferrer'
38+
aria-label={`Follow us on ${name}`}
39+
>
40+
<Image
41+
src={`/footer/${name}.svg`}
42+
alt={`${name} icon`}
43+
width={24}
44+
height={24}
45+
className='h-6 w-6'
46+
/>
47+
</Link>
48+
{index < Object.keys(ProfileSocialLinks).length - 1 && (
49+
<div className='ml-4 h-6 w-0.5 bg-[#2B2B2B]' aria-hidden='true' />
50+
)}
51+
</div>
52+
))}
53+
</div>
54+
<div className='flex gap-4'>
55+
<BoundlessButton>
56+
Follow <BellPlus />
57+
</BoundlessButton>
58+
<BoundlessButton variant='outline'>
59+
share{' '}
60+
<Image src='/share.svg' alt='Share icon' width={16} height={16} />
61+
</BoundlessButton>
62+
</div>
63+
</main>
64+
);
65+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use client';
2+
3+
import ProfileHeader from './ProfileHeader';
4+
import UserStats from './UserStats';
5+
import OrganizationsList from './OrganizationsList';
6+
import {
7+
UserProfile,
8+
UserStats as UserStatsType,
9+
Organization,
10+
} from '@/types/profile';
11+
12+
interface ProfileOverviewProps {
13+
username: string;
14+
}
15+
16+
export default function ProfileOverview({ username }: ProfileOverviewProps) {
17+
const mockProfile: UserProfile = {
18+
username: username,
19+
displayName: 'Collins Odumeje',
20+
bio: 'To build a secure, transparent, and trusted digital health ecosystem powered by Sonic blockchain for 280M lives in Indonesia.',
21+
avatarUrl: '/team/45d9f6d1-7e6d-41c7-8b16-a045d36e835f.png',
22+
socialLinks: {
23+
twitter: 'https://twitter.com/boundless',
24+
linkedin: 'https://linkedin.com/in/boundless',
25+
github: 'https://github.com/boundless',
26+
},
27+
};
28+
29+
const mockStats: UserStatsType = {
30+
organizations: 3,
31+
projects: 3,
32+
following: 10,
33+
followers: 5,
34+
};
35+
36+
const mockOrganizations: Organization[] = [
37+
{ name: 'Organization 1', avatarUrl: '/blog1.jpg' },
38+
{ name: 'Organization 2', avatarUrl: '/blog2.jpg' },
39+
];
40+
41+
return (
42+
<article className='flex w-[500px] flex-col gap-11 text-white'>
43+
<ProfileHeader profile={mockProfile} />
44+
<UserStats stats={mockStats} />
45+
<OrganizationsList organizations={mockOrganizations} />
46+
</article>
47+
);
48+
}

components/profile/UserStats.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { UserStats as UserStatsType } from '@/types/profile';
2+
3+
interface UserStatsProps {
4+
stats: UserStatsType;
5+
}
6+
7+
export default function UserStats({ stats }: UserStatsProps) {
8+
return (
9+
<div className='flex items-center gap-8'>
10+
<div className='flex items-center gap-1 text-xs font-medium text-gray-500'>
11+
<span className='text-base font-medium text-white'>
12+
{stats.organizations}
13+
</span>
14+
Organizations
15+
</div>
16+
<div className='flex items-center gap-1 text-xs font-medium text-gray-500'>
17+
<span className='text-base font-medium text-white'>
18+
{stats.projects}
19+
</span>
20+
Projects
21+
</div>
22+
<div className='flex items-center gap-1 text-xs font-medium text-gray-500'>
23+
<span className='text-base font-medium text-white'>
24+
{stats.following}
25+
</span>
26+
Following
27+
</div>
28+
<div className='flex items-center gap-1 text-xs font-medium text-gray-500'>
29+
<span className='text-base font-medium text-white'>
30+
{stats.followers}
31+
</span>
32+
Followers
33+
</div>
34+
</div>
35+
);
36+
}

lib/config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ export const socialLinks = {
1010
telegram: 'https://t.me/boundlessfi',
1111
gmail: 'hello@boundlessfi.xyz',
1212
};
13+
export const ProfileSocialLinks = {
14+
discord: 'https://discord.gg/boundlessfi',
15+
telegram: 'https://t.me/boundlessfi',
16+
github: 'https://github.com/boundlessfi',
17+
linkedin: 'https://www.linkedin.com/company/boundlesshq/',
18+
x: 'https://x.com/boundless_fi',
19+
};
1320

1421
export const backedBy = [
1522
{

package-lock.json

Lines changed: 0 additions & 105 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/share.svg

Lines changed: 3 additions & 0 deletions
Loading

types/profile.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export interface UserProfile {
2+
username: string;
3+
displayName: string;
4+
bio: string;
5+
avatarUrl: string;
6+
socialLinks: Record<string, string>;
7+
}
8+
9+
export interface UserStats {
10+
organizations: number;
11+
projects: number;
12+
following: number;
13+
followers: number;
14+
}
15+
16+
export interface Organization {
17+
name: string;
18+
avatarUrl: string;
19+
}

0 commit comments

Comments
 (0)