From d700c7d81ba2249b9a83bc836d85eb87ff228868 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Thu, 26 Mar 2026 16:51:19 +0000 Subject: [PATCH 1/4] [#569] Redesign profile header as social credibility trust dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Larger avatar (72px) with border - Trust dashboard grid: Farcaster card (username, FID, followers, Power/Pro badges), X/Twitter card (handle, followers, verified badge), Quotient Score card (score + rank), Wallet card (address + royalties) - Graceful degradation: each card only renders when data exists, wallet card always shown, wallet-only users get clean minimal profile - Mobile responsive: single column on mobile, 2-col grid on sm+ - Terminal/monospace aesthetic for stats (font-mono on numbers) - All data from existing users table — no new API calls Fixes #569 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/profile/[address]/page.tsx | 259 ++++++++++++++++++----------- 1 file changed, 159 insertions(+), 100 deletions(-) diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index ac9edd5b..7d4fadd9 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -170,7 +170,7 @@ export default function ProfilePage() { } // --------------------------------------------------------------------------- -// Profile Header (unchanged from #501) +// Profile Header — Social Credibility Trust Dashboard // --------------------------------------------------------------------------- function ProfileHeader({ @@ -205,28 +205,30 @@ function ProfileHeader({ cooldownRemaining: number; }) { const displayName = agentMeta?.name ?? fcProfile?.displayName ?? null; + const hasFarcaster = fcProfile != null; + const hasX = dbUser?.twitter != null; + const hasQuotient = dbUser?.quotient_score != null; return ( -
+
+ {/* Primary identity */}
- {/* Avatar */} {fcProfile?.pfpUrl ? ( // eslint-disable-next-line @next/next/no-img-element ) : ( -
+
{address.slice(2, 4).toUpperCase()}
)}
- {/* Name + badge */}

{fcLoading && agentLoading @@ -235,7 +237,7 @@ function ProfileHeader({

{!agentLoading && ( isAgent ? ( - + AI Agent ) : ( @@ -246,118 +248,175 @@ function ProfileHeader({ )}
- {/* Secondary identity line */} -
- {fcProfile && ( - - @{fcProfile.username} - - )} - {dbUser?.twitter && ( - - @{dbUser.twitter} - - )} + {/* Bio */} + {agentMeta?.description ? ( +

{agentMeta.description}

+ ) : fcProfile?.bio ? ( +

{fcProfile.bio}

+ ) : null} + + {/* Agent metadata */} + {agentMeta && ( +
+ {agentMeta.llmModel && ( + Model: {agentMeta.llmModel} + )} + {agentMeta.genre && ( + Genre: {agentMeta.genre} + )} + {agentMeta.registeredAt && ( + Registered: + {new Date(agentMeta.registeredAt).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })} + + )} +
+ )} +
+
+ + {/* Trust dashboard — social credibility cards */} +
+ {/* Farcaster card */} + {hasFarcaster && ( +
+
+ Farcaster +
+ {dbUser?.power_badge && ( + Power + )} + {dbUser?.is_pro_subscriber && ( + Pro + )} +
+
- {truncateAddress(address)} + @{fcProfile!.username} + {dbUser?.fid && ( + FID {dbUser.fid} + )} +
+
+ + {(dbUser?.follower_count ?? 0).toLocaleString()} + + followers +
+
+ + {(dbUser?.following_count ?? 0).toLocaleString()} + + following +
+
+ )} - {/* Agent metadata */} - {agentMeta && ( -
- {agentMeta.description && ( -

{agentMeta.description}

+ {/* X/Twitter card */} + {hasX && ( +
+
+ X / Twitter + {dbUser.x_verified && ( + Verified )} -
- {agentMeta.llmModel && ( - Model: {agentMeta.llmModel} - )} - {agentMeta.genre && ( - Genre: {agentMeta.genre} +
+ + @{dbUser.twitter} + + {dbUser.x_display_name && dbUser.x_display_name !== dbUser.twitter && ( + {dbUser.x_display_name} + )} + {(dbUser.x_followers_count != null || dbUser.x_following_count != null) && ( +
+ {dbUser.x_followers_count != null && ( +
+ + {dbUser.x_followers_count.toLocaleString()} + + followers +
)} - {agentMeta.registeredAt && ( - Registered: - {new Date(agentMeta.registeredAt).toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - })} - + {dbUser.x_following_count != null && ( +
+ + {dbUser.x_following_count.toLocaleString()} + + following +
)}
-
- )} - - {/* Farcaster bio (only show when no agent description is present) */} - {!agentMeta?.description && fcProfile?.bio && ( -

{fcProfile.bio}

- )} + )} +
+ )} - {/* Social stats from DB */} - {dbUser && ( -
- {dbUser.follower_count > 0 && ( - {dbUser.follower_count.toLocaleString()} followers - )} - {dbUser.following_count > 0 && ( - {dbUser.following_count.toLocaleString()} following - )} - {dbUser.quotient_score !== null && ( - QS: {dbUser.quotient_score} - )} - {dbUser.x_followers_count !== null && ( - X: {dbUser.x_followers_count.toLocaleString()} followers + {/* Quotient Score card */} + {hasQuotient && ( +
+ Quotient Score +
+ {dbUser!.quotient_score} + {dbUser!.quotient_rank != null && ( + Rank #{dbUser!.quotient_rank.toLocaleString()} )}
- )} +
+ )} - {/* Cumulative claimed royalties */} + {/* Wallet identity card — always shown */} +
+ Wallet + {claimedRoyalties && claimedRoyalties > BigInt(0) && ( -
- Royalties claimed: {formatPrice(formatUnits(claimedRoyalties, 18))} {RESERVE_LABEL} +
+ Royalties: {formatPrice(formatUnits(claimedRoyalties, 18))} {RESERVE_LABEL}
)} +
+
- {/* Refresh button (own profile only) */} - {isOwnProfile && ( -
- - {refreshError && ( - {refreshError} - )} -
+ {/* Refresh button (own profile only) */} + {isOwnProfile && ( +
+ + {refreshError && ( + {refreshError} )}
-
+ )}
); } From 908db7f57821f82e5598c60c7930fece1d51c62b Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Thu, 26 Mar 2026 16:53:19 +0000 Subject: [PATCH 2/4] [#569] Fix Farcaster link: use farcaster.com not farcaster.xyz Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/profile/[address]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index 7d4fadd9..1c04ff80 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -291,7 +291,7 @@ function ProfileHeader({ Date: Thu, 26 Mar 2026 16:56:21 +0000 Subject: [PATCH 3/4] [#569] Add copy-to-clipboard button next to wallet address Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/profile/[address]/page.tsx | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index 1c04ff80..b83458ad 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -377,7 +377,7 @@ function ProfileHeader({ {/* Wallet identity card — always shown */}
Wallet -
+ {claimedRoyalties && claimedRoyalties > BigInt(0) && (
@@ -421,6 +422,28 @@ function ProfileHeader({ ); } +// --------------------------------------------------------------------------- +// Copy to clipboard button +// --------------------------------------------------------------------------- + +function CopyButton({ text }: { text: string }) { + const [copied, setCopied] = useState(false); + return ( + + ); +} + // --------------------------------------------------------------------------- // Stories Tab — writer stats + story portfolio // --------------------------------------------------------------------------- From 5313a73c5b742ce2205d1e71da9f6335cb373b01 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Thu, 26 Mar 2026 16:59:27 +0000 Subject: [PATCH 4/4] [#569] Derive Farcaster card strictly from dbUser, not live API fallback hasFarcaster now checks dbUser.fid + dbUser.username instead of fcProfile. FC card uses dbUser fields so follower counts come from cached data, not the live API which doesn't return stats. Prevents misleading "0 followers" when user is uncached. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/profile/[address]/page.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index b83458ad..a71bb653 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -205,7 +205,7 @@ function ProfileHeader({ cooldownRemaining: number; }) { const displayName = agentMeta?.name ?? fcProfile?.displayName ?? null; - const hasFarcaster = fcProfile != null; + const hasFarcaster = dbUser?.fid != null && dbUser?.username != null; const hasX = dbUser?.twitter != null; const hasQuotient = dbUser?.quotient_score != null; @@ -291,16 +291,14 @@ function ProfileHeader({
- @{fcProfile!.username} + @{dbUser!.username} - {dbUser?.fid && ( - FID {dbUser.fid} - )} + FID {dbUser!.fid}