Move profile fields below bio

This commit is contained in:
lemon
2026-06-20 22:59:35 -07:00
parent 716579d91e
commit 4d148218ac
2 changed files with 22 additions and 39 deletions
+16 -22
View File
@@ -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}
/>
</div>
@@ -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({
<BioContent tags={metadataEvent?.tags}>{metadata.about}</BioContent>
</p>
)}
{fields.length > 0 && (
<div className="pt-2 space-y-3">
{fieldsContent}
</div>
)}
</div>
{/* 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 (
<div className={cn('flex flex-col gap-5', className)}>
{/* 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 && (
<section className="space-y-3">
<RailSectionHeader icon={null} title={t('profile.sections.profile')} />
<div className="space-y-3">{fieldsContent}</div>
</section>
)}
{/* Active campaigns */}
<RailCampaignsSection
campaigns={campaigns}
+5 -16
View File
@@ -1,5 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { ReactNode } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { useParams, Link } from 'react-router-dom';
@@ -768,8 +767,6 @@ interface ProfileTabContentProps {
btcPrice: number | undefined;
campaigns: ParsedCampaign[];
pledges: Action[];
fields: { label: string; value: string }[];
fieldsContent: ReactNode;
onTabChange: (tabId: string) => 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) => (
<ProfileFieldInline key={i} field={field} />
))}
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) => (
<ProfileFieldInline key={i} field={field} />
))}
isFollowing={isFollowing}
followPending={followPending}
canFollow={!!user}
@@ -1496,10 +1489,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) => (
<ProfileFieldInline key={i} field={field} />
))}
onTabChange={handleTabChange}
/>
</div>