Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
12 changes: 12 additions & 0 deletions src/app/profile/[did]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Metadata } from "next";
import HomeClient from "@/components/landing/home-client";

export const metadata: Metadata = {
title: "Profile",
description: "Certified profile and account overview.",
robots: { index: false, follow: false },
};

export default function ProfilePage() {
return <HomeClient />;
}
47 changes: 44 additions & 3 deletions src/components/landing/home-client.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { useParams, usePathname, useRouter } from "next/navigation";
import Link from "next/link";
import { Pencil } from "lucide-react";
import { useAuth } from "@/lib/auth/auth-context";
Expand All @@ -15,9 +15,12 @@ import Button from "@/components/ui/button";

export default function HomeClient() {
const router = useRouter();
const pathname = usePathname();
const params = useParams();
const urlDid = typeof params?.did === "string" ? decodeURIComponent(params.did) : null;
Comment thread
s-adamantine marked this conversation as resolved.
Resolved
const { isLoading, isAuthenticated, did } = useAuth();
const { profile, avatarUrl, bannerUrl, isFallback } = useProfile();
const { activeOrg } = useOrg();
const { activeOrg, groups, isLoading: orgsLoading, switchOrg } = useOrg();
const { orgProfile, orgMetadata, orgAvatarUrl, orgBannerUrl } = useOrgProfile();
const { handle } = useSession();

Expand All @@ -29,9 +32,47 @@ export default function HomeClient() {
// eslint-disable-next-line react-hooks/exhaustive-deps -- router is stable, omitting to prevent re-renders
}, [isLoading, isAuthenticated]);

// Canonicalize URL: `/` → `/profile/{did}` (of user or active org)
useEffect(() => {
if (isLoading || !isAuthenticated || !did) return;
if (pathname !== "/") return;
const targetDid = activeOrg?.groupDid || did;
router.replace(`/profile/${encodeURIComponent(targetDid)}`);
// eslint-disable-next-line react-hooks/exhaustive-deps -- router is stable
Comment thread
s-adamantine marked this conversation as resolved.
Resolved
}, [isLoading, isAuthenticated, did, pathname, activeOrg?.groupDid]);

// Sync active org with URL DID when on `/profile/[did]`
useEffect(() => {
if (isLoading || orgsLoading || !isAuthenticated || !did || !urlDid) return;

if (urlDid === did) {
if (activeOrg !== null) switchOrg(null);
return;
}

const matchingGroup = groups.find((g) => g.groupDid === urlDid);
if (matchingGroup) {
if (activeOrg?.groupDid !== matchingGroup.groupDid) switchOrg(matchingGroup);
return;
}

// URL DID doesn't match user or any of their groups — redirect to self.
const selfDid = activeOrg?.groupDid || did;
if (urlDid !== selfDid) {
router.replace(`/profile/${encodeURIComponent(selfDid)}`);
}
Comment thread
s-adamantine marked this conversation as resolved.
Resolved
// eslint-disable-next-line react-hooks/exhaustive-deps -- router is stable
}, [isLoading, orgsLoading, isAuthenticated, did, urlDid, groups, activeOrg?.groupDid]);

const initials = getInitials(profile?.displayName, did);

if (isLoading) {
// Stay in loading state when the URL DID hasn't been reconciled with
// activeOrg yet — prevents a flash of the wrong profile before the sync
// effect can switchOrg or redirect.
const displayedDid = activeOrg?.groupDid || did;
const urlOutOfSync = Boolean(urlDid) && urlDid !== displayedDid;

if (isLoading || (isAuthenticated && pathname === "/") || urlOutOfSync) {
return (
<div className="loading-screen">
<div className="loading-screen__inner">
Expand Down
24 changes: 15 additions & 9 deletions src/components/layout/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ import { useOrg } from "@/lib/groups/org-context";
import { useOrgProfile } from "@/hooks/use-org-profile";
import { Menu, X, ChevronDown, LogOut } from "lucide-react";

const PERSONAL_NAV_LINKS = [
{ href: "/", label: "Profile" },
const PERSONAL_NAV_LINKS = (profileHref: string) => [
{ href: profileHref, label: "Profile" },
{ href: "/groups", label: "Groups" },
{ href: "/connected-apps", label: "Apps" },
{ href: "/settings", label: "Settings" },
];

const ORG_NAV_LINKS = [
{ href: "/", label: "Profile" },
const ORG_NAV_LINKS = (profileHref: string) => [
{ href: profileHref, label: "Profile" },
{ href: "/connected-apps", label: "Apps" },
{ href: "/settings", label: "Settings" },
];
Expand Down Expand Up @@ -145,7 +145,10 @@ const Navbar: React.FC = () => {
}, [sheetExpanded]);

// Derive display state from org context
const navLinks = activeOrg ? ORG_NAV_LINKS : PERSONAL_NAV_LINKS;
const profileDid = activeOrg?.groupDid || did;
const profileHref = profileDid ? `/profile/${encodeURIComponent(profileDid)}` : "/";
const selfProfileHref = did ? `/profile/${encodeURIComponent(did)}` : "/";
const navLinks = activeOrg ? ORG_NAV_LINKS(profileHref) : PERSONAL_NAV_LINKS(profileHref);
const displayName = activeOrg
? (activeOrg.displayName || activeOrg.handle)
: profile?.displayName;
Expand All @@ -169,6 +172,9 @@ const Navbar: React.FC = () => {

const isActive = (href: string) => {
if (href === "/") return pathname === "/" || pathname === "/settings/edit-profile";
if (href.startsWith("/profile/")) {
return pathname === "/" || pathname.startsWith("/profile/") || pathname === "/settings/edit-profile";
}
Comment on lines 173 to +177

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep “Profile” active on org edit-profile screens.

When org mode clicks Edit, HomeClient routes to /groups/{groupDid}/edit-profile, but this predicate only treats /settings/edit-profile as part of the profile section. The result is that the Profile nav loses its active state on the group edit screen. The same matcher was added in src/components/layout/sidebar.tsx, so it would be worth sharing one helper for both places.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/layout/navbar.tsx` around lines 173 - 177, The isActive(href:
string) predicate in Navbar incorrectly omits org group edit-profile routes
(e.g. /groups/{groupDid}/edit-profile), so update the logic in the isActive
function to treat any path that ends with or includes "/edit-profile" (in
addition to "/settings/edit-profile") as part of the Profile section; refactor
this matcher into a shared helper (e.g. isProfilePath or isProfileActive) that
both src/components/layout/navbar.tsx and src/components/layout/sidebar.tsx can
import and use, ensuring the new helper checks for "/" root, "/profile/*" and
any "/.../edit-profile" patterns.

return pathname.startsWith(href);
};

Expand Down Expand Up @@ -216,7 +222,7 @@ const Navbar: React.FC = () => {
<button
role="menuitem"
className={`account-switcher__item ${!activeOrg ? "account-switcher__item--active" : ""}`}
onClick={() => { switchOrg(null); setSwitcherOpen(false); router.push("/"); }}
onClick={() => { switchOrg(null); setSwitcherOpen(false); router.push(selfProfileHref); }}
>
<Avatar
src={avatarUrl || undefined}
Expand Down Expand Up @@ -253,7 +259,7 @@ const Navbar: React.FC = () => {
onClick={() => {
switchOrg(org);
setSwitcherOpen(false);
router.push("/");
router.push(`/profile/${encodeURIComponent(org.groupDid)}`);
}}
>
<Avatar
Expand Down Expand Up @@ -306,7 +312,7 @@ const Navbar: React.FC = () => {
<button
role="menuitem"
className={`account-switcher__item ${!activeOrg ? "account-switcher__item--active" : ""}`}
onClick={() => { switchOrg(null); setSwitcherOpen(false); router.push("/"); }}
onClick={() => { switchOrg(null); setSwitcherOpen(false); router.push(selfProfileHref); }}
>
<Avatar
src={avatarUrl || undefined}
Expand Down Expand Up @@ -343,7 +349,7 @@ const Navbar: React.FC = () => {
onClick={() => {
switchOrg(org);
setSwitcherOpen(false);
router.push("/");
router.push(`/profile/${encodeURIComponent(org.groupDid)}`);
}}
>
<Avatar
Expand Down
12 changes: 9 additions & 3 deletions src/components/layout/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@ interface SidebarProps {
}

export default function Sidebar({ isOpen = false, onClose }: SidebarProps) {
const { signOut } = useAuth();
const { signOut, did } = useAuth();
const { profile, avatarUrl } = useProfile();
const { handle } = useSession();
const { activeOrg } = useOrg();
const { orgAvatarUrl } = useOrgProfile();
const pathname = usePathname();
const profileDid = activeOrg?.groupDid || did;
const profileHref = profileDid ? `/profile/${encodeURIComponent(profileDid)}` : "/";
const isProfileActive =
pathname === "/" ||
pathname.startsWith("/profile/") ||
pathname === "/settings/edit-profile";

// Close sidebar on navigation (mobile)
useEffect(() => {
Expand Down Expand Up @@ -60,8 +66,8 @@ export default function Sidebar({ isOpen = false, onClose }: SidebarProps) {
<ul className="sidebar__menu">
<li>
<Link
href="/"
className={`sidebar__item ${pathname === "/" || pathname === "/settings/edit-profile" ? "sidebar__item--active" : ""}`}
href={profileHref}
className={`sidebar__item ${isProfileActive ? "sidebar__item--active" : ""}`}
>
<User size={18} />
Profile
Expand Down