-
-
Notifications
You must be signed in to change notification settings - Fork 16
feat: Implement dynamic /[username] public profile route #100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,29 @@ | ||
| import React from "react"; | ||
| import { DashboardSidebar } from "@/components/layout/SideBar"; | ||
| import { DashboardNavbar } from "@/components/layout/DashboardNavBar"; | ||
|
|
||
| export default function PublicProfileLayout({ | ||
| children, | ||
| }: { | ||
| children: React.ReactNode; | ||
| }) { | ||
| return <section className="min-h-screen bg-[#0a0a0a]">{children}</section>; | ||
| return ( | ||
| <div className="flex h-screen overflow-hidden bg-dashboard-bg"> | ||
| {/* Sidebar */} | ||
| <div className="no-scrollbar h-full"> | ||
| <DashboardSidebar /> | ||
| </div> | ||
|
|
||
| {/* Main content area (navbar + page) */} | ||
| <div className="flex min-w-0 flex-1 flex-col overflow-hidden"> | ||
| {/* Top navbar */} | ||
| <DashboardNavbar /> | ||
|
|
||
| {/* Page content */} | ||
| <main className="no-scrollbar flex-1 overflow-y-auto bg-app-bg"> | ||
| {children} | ||
| </main> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,78 @@ | ||
| "use client"; | ||
|
|
||
| import React from "react"; | ||
| import { useParams } from "next/navigation"; | ||
| import DashboardMetrics from "@/components/dashboard/DashboardMetrics"; | ||
| import { UserTokenList } from "@/components/dashboard/UserTokenList"; | ||
| import { TokenData } from "@/components/dashboard/TokenList"; | ||
|
|
||
| const mockUserTokens: TokenData[] = [ | ||
| { | ||
| tokenId: "0xa1b2c3", | ||
| name: "Vaccine Certificate", | ||
| type: "Health", | ||
| expiresIn: "3 years", | ||
| endorsements: 87, | ||
| }, | ||
| { | ||
| tokenId: "0xd4e5f6", | ||
| name: "University Degree", | ||
| type: "Education", | ||
| expiresIn: "Never", | ||
| endorsements: 142, | ||
| }, | ||
| { | ||
| tokenId: "0xg7h8i9", | ||
| name: "KYC Verified", | ||
| type: "Identity", | ||
| expiresIn: "2 years", | ||
| endorsements: 56, | ||
| }, | ||
| { | ||
| tokenId: "0xj0k1l2", | ||
| name: "Employment Record", | ||
| type: "Work", | ||
| expiresIn: "1 year", | ||
| endorsements: 24, | ||
| }, | ||
| { | ||
| tokenId: "0xm3n4o5", | ||
| name: "Open Source Contributor", | ||
| type: "Skill", | ||
| expiresIn: "Never", | ||
| endorsements: 203, | ||
| }, | ||
| ]; | ||
|
|
||
| export default function UsernamePage() { | ||
| const params = useParams<{ username: string }>(); | ||
| const username = params?.username ?? ""; | ||
|
|
||
| export default async function UsernamePage({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ username: string }>; | ||
| }) { | ||
| const { username } = await params; | ||
| return ( | ||
| <main className="flex min-h-screen items-center justify-center bg-app-bg"> | ||
| <h1 className="font-utsaha text-2xl font-semibold text-white"> | ||
| @{username} | ||
| </h1> | ||
| </main> | ||
| <div className="flex h-full flex-col gap-8 bg-app-bg pb-12"> | ||
| <DashboardMetrics | ||
| name={username} | ||
| age={20} | ||
| nationality="Indian" | ||
| walletAddress="0x9032345320958093280943r82" | ||
| endorsers={128} | ||
| lastUpdated="1 Day Ago" | ||
| trustScore={92} | ||
| trustFlags="None" | ||
| trustDescription="Their On-Chain Reputation is excellent" | ||
| totalEndorsements={70} | ||
| activeTokens={14} | ||
| socials={3} | ||
| badgesEarned="300+ Trust Received" | ||
| /> | ||
|
|
||
| <div className="px-4 sm:px-6 md:pr-14 md:pl-10"> | ||
| <UserTokenList | ||
| tokens={mockUserTokens} | ||
| onEndorse={(id) => console.log("Endorsing token:", id)} | ||
| onRevoke={(id) => console.log("Revoking token:", id)} | ||
| /> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,58 @@ | ||||||
| "use client"; | ||||||
|
|
||||||
| import React from "react"; | ||||||
| import { TokenCard } from "../cards/TokenCard"; | ||||||
| import { TokenData } from "./TokenList"; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win Prefer
♻️ Suggested fix-import { TokenData } from "./TokenList";
+import type { TokenData } from "./TokenList";📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||
|
|
||||||
| interface UserTokenListProps { | ||||||
| tokens: TokenData[]; | ||||||
| className?: string; | ||||||
| onRevoke?: (tokenId: string) => void; | ||||||
| onEndorse?: (tokenId: string) => void; | ||||||
| onViewAll?: (tokenId: string) => void; | ||||||
| } | ||||||
|
|
||||||
| export function UserTokenList({ | ||||||
| tokens, | ||||||
| className = "", | ||||||
| onRevoke, | ||||||
| onEndorse, | ||||||
| onViewAll, | ||||||
| }: UserTokenListProps) { | ||||||
| return ( | ||||||
| <div | ||||||
| className={`w-full overflow-hidden rounded-2xl border border-card-border bg-card-bg p-4 sm:p-6 ${className}`} | ||||||
| > | ||||||
| {/* Section Title */} | ||||||
| <h2 className="mb-5 font-utsaha text-xl text-white md:mb-6 md:text-2xl"> | ||||||
| Tokens | ||||||
| </h2> | ||||||
|
|
||||||
| {/* Token Cards */} | ||||||
| <div className="flex flex-col gap-3"> | ||||||
| {tokens.length === 0 ? ( | ||||||
| <div className="flex items-center justify-center py-12 font-utsaha text-lg text-gray-500"> | ||||||
| No tokens found. | ||||||
| </div> | ||||||
| ) : ( | ||||||
| tokens.map((token) => ( | ||||||
| <TokenCard | ||||||
| key={token.tokenId} | ||||||
| variant="discover" | ||||||
| tokenId={token.tokenId} | ||||||
| name={token.name} | ||||||
| type={token.type} | ||||||
| expiresIn={token.expiresIn} | ||||||
| endorsements={token.endorsements} | ||||||
| onRevoke={onRevoke ? () => onRevoke(token.tokenId) : undefined} | ||||||
| onEndorse={onEndorse ? () => onEndorse(token.tokenId) : undefined} | ||||||
| onViewAll={onViewAll ? () => onViewAll(token.tokenId) : undefined} | ||||||
| /> | ||||||
| )) | ||||||
| )} | ||||||
| </div> | ||||||
| </div> | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| export default UserTokenList; | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,13 @@ export function DashboardNavbar() { | |
| setIsCreateModalOpen(false); | ||
| }; | ||
|
|
||
| const knownRoutes = ["/", "/home", "/dashboard", "/discover", "/settings"]; | ||
| const firstSegment = pathname?.split("/").filter(Boolean)[0] ?? ""; | ||
| const isUserProfile = | ||
| pathname !== "/" && | ||
| !knownRoutes.includes(pathname) && | ||
| firstSegment.length > 0; | ||
|
Comment on lines
+23
to
+28
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win Refactor route detection to use path segments for robustness. The current logic checks the full ♻️ Suggested refactor- const knownRoutes = ["/", "/home", "/dashboard", "/discover", "/settings"];
+ const knownRouteSegments = ["home", "dashboard", "discover", "settings"];
const firstSegment = pathname?.split("/").filter(Boolean)[0] ?? "";
const isUserProfile =
pathname !== "/" &&
- !knownRoutes.includes(pathname) &&
- firstSegment.length > 0;
+ firstSegment !== "" &&
+ !knownRouteSegments.includes(firstSegment);This approach correctly handles both 🤖 Prompt for AI Agents |
||
|
|
||
| const isDiscover = pathname === "/discover"; | ||
|
|
||
| const getPageTitle = () => { | ||
|
|
@@ -33,9 +40,11 @@ export function DashboardNavbar() { | |
| <> | ||
| <nav className="flex h-[72px] w-full shrink-0 items-center justify-between border-b border-white/5 bg-dashboard-bg pr-5 pl-16 lg:px-8"> | ||
| <div className="flex min-w-0 flex-1 items-center gap-4"> | ||
| <h1 className="shrink-0 font-utsaha text-xl tracking-wide text-white"> | ||
| {getPageTitle()} | ||
| </h1> | ||
| {!isUserProfile && ( | ||
| <h1 className="shrink-0 font-utsaha text-xl tracking-wide text-white"> | ||
| {getPageTitle()} | ||
| </h1> | ||
| )} | ||
|
|
||
| {isDiscover && ( | ||
| <SearchBar placeholder="Search by Token ID or Decentralized ID…" /> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial | ⚡ Quick win
Prefer
import typefor type-only imports.TokenDatais used only as a type annotation and should be imported withimport typefor better clarity and tree-shaking.♻️ Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents
Source: Coding guidelines