Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions src/app/profile/[username]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,22 @@ import { ProjectShowcaseSection } from '@/components/profile/ProjectShowcaseSect
import { ExportBar } from '@/components/profile/ExportBar';

interface Props {
params: { username: string };
params: Promise<{ username: string }>;
}

export async function generateStaticParams() {
return [
{ username: 'dummy' },
{ username: 'kew7p1pbj7WoX66uGH2ZMcg79RB3' }
];
}

export const dynamicParams = false;

// Generate OpenGraph metadata dynamically
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const profile = await getPublicProfileByUsername(params.username);
const { username } = await params;
const profile = await getPublicProfileByUsername(username);
if (!profile) return { title: 'Profile not found' };

return {
Expand All @@ -31,7 +41,8 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
}

export default async function PublicProfilePage({ params }: Props) {
const profile = await getPublicProfileByUsername(params.username);
const { username } = await params;
const profile = await getPublicProfileByUsername(username);

if (!profile) notFound();

Expand Down
102 changes: 68 additions & 34 deletions src/components/profile/DevCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import {
query,
where,
getCountFromServer,
doc,
onSnapshot,
} from 'firebase/firestore';
import { db } from '@/lib/firebase';
import { useNotificationActions } from '@/stores/ui-store';
Expand Down Expand Up @@ -183,17 +185,49 @@ export default function DevCard({ user }: { user: any }) {
const [avatarLoadFailed, setAvatarLoadFailed] = useState(false);
const { showSuccess, showError } = useNotificationActions();

const [realTimeUser, setRealTimeUser] = useState(user);

useEffect(() => {
setRealTimeUser(user);
}, [user]);

useEffect(() => {
if (!user?.uid) return;

const collectionName = user.role === 'admin' ? 'admins' : 'members';
const docId = user.role === 'admin' ? user.email?.toLowerCase() : user.uid;

if (!docId) return;

const docRef = doc(db, collectionName, docId);
const unsubscribe = onSnapshot(docRef, (docSnap) => {
if (docSnap.exists()) {
const data = docSnap.data();
setRealTimeUser((prev: any) => ({
...prev,
...data,
uid: user.uid,
role: user.role,
}));
}
}, (error) => {
console.error("Error listening to profile changes in DevCard:", error);
});

return () => unsubscribe();
}, [user?.uid, user?.role, user?.email]);

useEffect(() => {
const fetch = async () => {
if (!user?.points) {
if (!realTimeUser?.points) {
setRankLoading(false);
return;
}
try {
const snap = await getCountFromServer(
query(
collection(db, 'leaderboard'),
where('points', '>', user.points)
where('points', '>', realTimeUser.points)
)
);
setRank(snap.data().count + 1);
Expand All @@ -204,13 +238,13 @@ export default function DevCard({ user }: { user: any }) {
}
};
fetch();
}, [user?.points]);
}, [realTimeUser?.points]);

useEffect(() => {
setShowSkeleton(true);
const timer = setTimeout(() => setShowSkeleton(false), 650);
return () => clearTimeout(timer);
}, [user?.uid]);
}, [realTimeUser?.uid]);

useEffect(() => {
const t = setTimeout(() => setLangMounted(true), 500);
Expand All @@ -219,34 +253,34 @@ export default function DevCard({ user }: { user: any }) {

useEffect(() => {
setAvatarLoadFailed(false);
}, [user?.photoURL]);
}, [realTimeUser?.photoURL]);

const levelInfo = calculateLevel(user?.points ?? 0);
const levelInfo = calculateLevel(realTimeUser?.points ?? 0);
const level = levelInfo.currentLevel;
const levelColor = resolveLevelColor(level.color);
const levelBg = resolveLevelBg(level.bg);

const earnedBadges = (user?.achievements ?? [])
const earnedBadges = (realTimeUser?.achievements ?? [])
.filter((id: string) => BADGE_REGISTRY[id])
.map((id: string) => ({ id, ...BADGE_REGISTRY[id] }));

const topBadges = earnedBadges.slice(0, 4);
const extraCount = Math.max(0, earnedBadges.length - 4);
const topLangs = (
(user?.githubStats?.topLanguages ?? []) as {
(realTimeUser?.githubStats?.topLanguages ?? []) as {
language: string;
count: number;
}[]
).slice(0, 4);
const totalLang = topLangs.reduce((s, l) => s + l.count, 0);

const animXP = useAnimatedCount(user?.points ?? 0);
const animStreak = useAnimatedCount(user?.streak ?? 0, 900);
const animXP = useAnimatedCount(realTimeUser?.points ?? 0);
const animStreak = useAnimatedCount(realTimeUser?.streak ?? 0, 900);

const profileUrl =
typeof window !== 'undefined'
? `${window.location.origin}/u/${user?.uid}`
: `devpath.in/u/${user?.uid}`;
? `${window.location.origin}/u/${realTimeUser?.uid}`
: `devpath.in/u/${realTimeUser?.uid}`;

const waitForCardImages = async (root: HTMLElement) => {
const imgs = Array.from(root.querySelectorAll('img'));
Expand Down Expand Up @@ -306,7 +340,7 @@ export default function DevCard({ user }: { user: any }) {
const url = canvas.toDataURL('image/png');
const a = document.createElement('a');
a.href = url;
a.download = `devcard-${user?.name?.replace(/\s+/g, '-').toLowerCase() ?? 'devcard'}.png`;
a.download = `devcard-${realTimeUser?.name?.replace(/\s+/g, '-').toLowerCase() ?? 'devcard'}.png`;
a.click();
showSuccess('DevCard downloaded successfully.');
} catch {
Expand Down Expand Up @@ -435,10 +469,10 @@ export default function DevCard({ user }: { user: any }) {
>
<motion.div className={styles.avatarRing} variants={item}>
<div className={styles.avatarRingInner} />
{user?.photoURL && !avatarLoadFailed ? (
{realTimeUser?.photoURL && !avatarLoadFailed ? (
<Image
src={user.photoURL}
alt={user?.name ?? 'Developer'}
src={realTimeUser.photoURL}
alt={realTimeUser?.name ?? 'Developer'}
fill
className={styles.avatar}
unoptimized
Expand All @@ -449,12 +483,12 @@ export default function DevCard({ user }: { user: any }) {
/>
) : (
<div className={styles.avatarFallback}>
{user?.name?.charAt(0)?.toUpperCase() ?? '?'}
{realTimeUser?.name?.charAt(0)?.toUpperCase() ?? '?'}
</div>
)}
</motion.div>
<motion.h2 className={styles.name} variants={item}>
{user?.name ?? 'Developer'}
{realTimeUser?.name ?? 'Developer'}
</motion.h2>
<motion.div
className={styles.levelBadge}
Expand All @@ -469,25 +503,25 @@ export default function DevCard({ user }: { user: any }) {
<span>{level.name}</span>
</motion.div>
<motion.div className={styles.meta} variants={item}>
{(user?.city || user?.state) && (
{(realTimeUser?.city || realTimeUser?.state) && (
<span className={styles.metaRow}>
<MapPin size={10} />
{[user.city, user.state].filter(Boolean).join(', ')}
{[realTimeUser.city, realTimeUser.state].filter(Boolean).join(', ')}
</span>
)}
<span className={styles.metaRow}>
<Calendar size={10} />
Joined {fmtDate(user?.createdAt)}
Joined {fmtDate(realTimeUser?.createdAt)}
</span>
{user?.githubStats?.username && (
{realTimeUser?.githubStats?.username && (
<span className={styles.metaRow}>
<Github size={10} />
{user.githubStats.username}
{realTimeUser.githubStats.username}
</span>
)}
{user?.linkedin && (
{realTimeUser?.linkedin && (
<a
href={user.linkedin}
href={realTimeUser.linkedin}
target="_blank"
rel="noopener noreferrer"
className={styles.metaRowLink}
Expand All @@ -496,9 +530,9 @@ export default function DevCard({ user }: { user: any }) {
LinkedIn
</a>
)}
{user?.instagram && (
{realTimeUser?.instagram && (
<a
href={user.instagram}
href={realTimeUser.instagram}
target="_blank"
rel="noopener noreferrer"
className={styles.metaRowLink}
Expand Down Expand Up @@ -609,7 +643,7 @@ export default function DevCard({ user }: { user: any }) {
<Brain size={14} strokeWidth={2.5} />
</div>
<span className={styles.statValue}>
{user?.completedQuizzes?.length ?? 0}
{realTimeUser?.completedQuizzes?.length ?? 0}
</span>
<span className={styles.statLabel}>Quizzes</span>
</div>
Expand All @@ -621,23 +655,23 @@ export default function DevCard({ user }: { user: any }) {
color: '#818cf8',
}}
>
{user?.githubStats?.connected ? (
{realTimeUser?.githubStats?.connected ? (
<Github size={14} strokeWidth={2.5} />
) : (
<Users size={14} strokeWidth={2.5} />
)}
</div>
<span className={styles.statValue}>
{user?.githubStats?.connected
{realTimeUser?.githubStats?.connected
? fmtPoints(
user.githubStats.totalStars ??
user.githubStats.stars ??
realTimeUser.githubStats.totalStars ??
realTimeUser.githubStats.stars ??
0
)
: (user?.followers?.length ?? 0)}
: (realTimeUser?.followers?.length ?? 0)}
</span>
<span className={styles.statLabel}>
{user?.githubStats?.connected ? 'GH Stars' : 'Followers'}
{realTimeUser?.githubStats?.connected ? 'GH Stars' : 'Followers'}
</span>
</div>
</motion.div>
Expand Down
Loading