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 && (
-
- )}
-
{/* 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}
/>
>