diff --git a/src/components/profile/ProfileIdentityRail.tsx b/src/components/profile/ProfileIdentityRail.tsx index 413ab369..98daee2f 100644 --- a/src/components/profile/ProfileIdentityRail.tsx +++ b/src/components/profile/ProfileIdentityRail.tsx @@ -99,7 +99,7 @@ const RAIL_ORG_LIMIT = 4; * ProfileIdentityRail — the left rail of the two-column profile. * * Holds everything that's a *standing fact* about the profile: who they - * are (avatar, name, bio), what they're raising for (active campaigns), + * are (avatar, name, bio, profile fields), what they're raising for (active campaigns), * who they organize with (orgs), key counts (followers / following / * campaigns / pledges / raised), and the freeform profile fields. * @@ -185,6 +185,8 @@ export function ProfileIdentityRail({ metadataEvent={metadataEvent} displayName={displayName} websiteHref={websiteHref} + fields={fields} + fieldsContent={fieldsContent} isFollowing={isFollowing} followPending={followPending} canFollow={canFollow} @@ -208,8 +210,6 @@ export function ProfileIdentityRail({ campaignStats={campaignStats} pledges={pledges} btcPrice={btcPrice} - fields={fields} - fieldsContent={fieldsContent} onTabChange={onTabChange} /> @@ -227,6 +227,8 @@ interface ProfileIdentityHeaderProps { displayName: string; /** Pre-sanitized website URL (`undefined` if none / unsafe). */ websiteHref: string | undefined; + fields: { label: string; value: string }[]; + fieldsContent: ReactNode; isFollowing: boolean; followPending: boolean; canFollow: boolean; @@ -253,8 +255,8 @@ interface ProfileIdentityHeaderProps { } /** - * The fixed identity block: name, NIP-05, website, bio, action bar, and - * top-level stat row (Followers / Following / Raised). + * The fixed identity block: name, NIP-05, website, stats, bio, profile + * fields, and action bar. * * Rendered inside `ProfileIdentityRail` on desktop and directly above the * tab bar on mobile. Does NOT include the avatar — that lives outside any @@ -267,6 +269,8 @@ export function ProfileIdentityHeader({ metadataEvent, displayName, websiteHref, + fields, + fieldsContent, isFollowing, followPending, canFollow, @@ -325,6 +329,11 @@ export function ProfileIdentityHeader({ {metadata.about}

)} + {fields.length > 0 && ( +
+ {fieldsContent} +
+ )} {/* Action bar — wraps onto multiple rows in a 340px-wide rail. On @@ -347,7 +356,7 @@ export function ProfileIdentityHeader({ ); } -// ─── Overview sections (campaigns / latest pledge / orgs / fields) ────────── +// ─── Overview sections (campaigns / latest pledge / orgs) ─────────────────── interface ProfileOverviewSectionsProps { pubkey: string; @@ -356,8 +365,6 @@ interface ProfileOverviewSectionsProps { campaignStats: ProfileCampaignStats; pledges: Action[]; btcPrice: number | undefined; - fields: { label: string; value: string }[]; - fieldsContent: ReactNode; onTabChange: (tabId: string) => void; /** Render the Organizations grid inline (default true). Set false on * mobile when "Community" is a dedicated tab and orgs should not also @@ -369,7 +376,7 @@ interface ProfileOverviewSectionsProps { /** * The collection of secondary rail sections: active campaigns, a fallback * "latest pledge" card when there are no campaigns, organizations the - * profile founded/moderates, and freeform kind-0 profile fields. + * profile founded/moderates. * * On desktop these stack inside the identity rail. On mobile they become * the content of the "Overview" tab (with `showOrganizations={false}` so @@ -382,25 +389,12 @@ export function ProfileOverviewSections({ campaignStats, pledges, btcPrice, - fields, - fieldsContent, onTabChange, showOrganizations = true, className, }: ProfileOverviewSectionsProps) { - const { t } = useTranslation(); return (
- {/* Profile fields (rendered upstream) — placed first so the - profile's own freeform metadata (links, addresses, etc.) is - the first thing visitors read, ahead of campaigns/orgs. */} - {fields.length > 0 && ( -
- -
{fieldsContent}
-
- )} - {/* Active campaigns */} void; } @@ -789,8 +786,6 @@ function ProfileTabContent({ btcPrice, campaigns, pledges, - fields, - fieldsContent, onTabChange, }: ProfileTabContentProps) { if (activeTab === 'overview') { @@ -803,8 +798,6 @@ function ProfileTabContent({ campaignStats={profileCampaignStats} pledges={pledges} btcPrice={btcPrice} - fields={fields} - fieldsContent={fieldsContent} onTabChange={onTabChange} // Organizations has its own tab on mobile. showOrganizations={false} @@ -858,7 +851,7 @@ function ProfileTabContent({ // Desktop (lg+) keeps the focused content set; the rail to the // left already shows the profile's Overview information (campaigns, -// orgs, fields), so duplicating it as a tab would be redundant. +// orgs), so duplicating it as a tab would be redundant. // "Groups" and "Pledges" are temporarily hidden. const DESKTOP_TAB_LABEL_KEYS = ['activity', 'campaigns'] as const; @@ -1388,10 +1381,6 @@ function FollowersListModal({ pubkey, open, onOpenChange, displayName }: Followe btcPrice={btcPrice} campaigns={profileCampaignStats.campaigns} pledges={(allActions ?? []).filter((a) => a.pubkey === pubkey)} - fields={fields} - fieldsContent={fields.map((field, i) => ( - - ))} onTabChange={handleTabChange} /> )} @@ -1461,6 +1450,10 @@ function FollowersListModal({ pubkey, open, onOpenChange, displayName }: Followe : `https://${metadata.website}`; return sanitizeUrl(candidate); })()} + fields={fields} + fieldsContent={fields.map((field, i) => ( + + ))} isFollowing={isFollowing} followPending={followPending} canFollow={!!user} @@ -1496,11 +1489,7 @@ function FollowersListModal({ pubkey, open, onOpenChange, displayName }: Followe btcPrice={btcPrice} campaigns={profileCampaignStats.campaigns} pledges={(allActions ?? []).filter((a) => a.pubkey === pubkey)} - fields={fields} - fieldsContent={fields.map((field, i) => ( - - ))} - onTabChange={handleTabChange} + onTabChange={handleTabChange} />