From 1dbac901084f327559cfc05cea4e10eb024cc363 Mon Sep 17 00:00:00 2001 From: Chad Curtis Date: Thu, 21 May 2026 22:26:36 -0500 Subject: [PATCH] Expose search in TopNav and add Agora kind presets to the Kind picker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a Search icon button to the TopNav right cluster (visible on all breakpoints) that links to /search, so users no longer have to dig into the mobile drawer or profile menu to reach search. Surface Agora's main content kinds in the Search filter Kind picker: - Add Campaigns (33863) and Pledges (36639) to buildKindOptions(), since they are first-class Agora content but live outside EXTRA_KINDS (which drives the sidebar / feed-settings UI and shouldn't be polluted with kinds that don't have their own toggleable feed/sidebar items). - Group the curated Agora set — Campaigns, Pledges, Communities, Posts, Articles, Events, Polls, Photos, Videos — under an 'Agora content' section at the top of the KindPicker dropdown, with the long-tail list of every other supported kind under an 'All kinds' section below. When the user types in the search box the partition collapses to a flat result list. --- src/components/SavedFeedFiltersEditor.tsx | 38 +++++++++++++++++- src/components/TopNav.tsx | 17 +++++++- src/lib/feedFilterUtils.ts | 47 ++++++++++++++++++++++- 3 files changed, 98 insertions(+), 4 deletions(-) diff --git a/src/components/SavedFeedFiltersEditor.tsx b/src/components/SavedFeedFiltersEditor.tsx index 87e3b256..fe93973b 100644 --- a/src/components/SavedFeedFiltersEditor.tsx +++ b/src/components/SavedFeedFiltersEditor.tsx @@ -44,7 +44,7 @@ type KindOption = { // ─── Kind options (built once) ─────────────────────────────────────────────── -import { buildKindOptions } from '@/lib/feedFilterUtils'; +import { buildKindOptions, AGORA_PRESET_KIND_VALUES } from '@/lib/feedFilterUtils'; // ─── useScrollCarets ───────────────────────────────────────────────────────── @@ -152,6 +152,25 @@ export function KindPicker({ value, options, onChange }: { ); }, [options, search]); + // Partition into Agora preset (top of picker) and the rest. + // When the user is searching, we skip the partition and show a flat list. + const presetSet = useMemo(() => new Set(AGORA_PRESET_KIND_VALUES), []); + const { presetOptions, otherOptions } = useMemo(() => { + if (search) return { presetOptions: [], otherOptions: filtered }; + const preset: KindOption[] = []; + const other: KindOption[] = []; + // Preserve AGORA_PRESET_KIND_VALUES order for the preset section. + const byValue = new Map(filtered.map((o) => [o.value, o])); + for (const v of AGORA_PRESET_KIND_VALUES) { + const opt = byValue.get(v); + if (opt) preset.push(opt); + } + for (const o of filtered) { + if (!presetSet.has(o.value)) other.push(o); + } + return { presetOptions: preset, otherOptions: other }; + }, [filtered, presetSet, search]); + const selected = value === 'all' || value === 'custom' ? null : options.find((o) => o.value === value); const SelectedIcon = selected?.icon; @@ -199,7 +218,22 @@ export function KindPicker({ value, options, onChange }: { {canScrollUp && startScroll('up')} onMouseLeave={stopScroll} />}
{!search && handleSelect('all')} />} - {filtered.map((opt) => ( + {!search && presetOptions.length > 0 && ( + <> +
+ Agora content +
+ {presetOptions.map((opt) => ( + handleSelect(opt.value)} /> + ))} + {otherOptions.length > 0 && ( +
+ All kinds +
+ )} + + )} + {otherOptions.map((opt) => ( handleSelect(opt.value)} /> ))} {(!search || 'custom'.includes(search.toLowerCase())) && ( diff --git a/src/components/TopNav.tsx b/src/components/TopNav.tsx index 5449d1db..0d0531ec 100644 --- a/src/components/TopNav.tsx +++ b/src/components/TopNav.tsx @@ -1,5 +1,5 @@ import { useState, type ComponentType } from 'react'; -import { Link, NavLink } from 'react-router-dom'; +import { Link, NavLink, useNavigate } from 'react-router-dom'; import { Activity, Bell, @@ -54,6 +54,9 @@ export function TopNav() { const { user } = useCurrentUser(); const { orderedItems } = useFeedSettings(); const [mobileOpen, setMobileOpen] = useState(false); + const navigate = useNavigate(); + + const goToSearch = () => navigate('/search'); return (
@@ -89,6 +92,18 @@ export function TopNav() { {/* Right cluster */}
+ {/* Search — navigates to the /search page. Visible on all + breakpoints so users can always reach search from the chrome. */} + + {/* LoginArea handles both logged-in (account avatar dropdown) and logged-out (Log in / Sign up) states. We render it inline-flex and let it style its own children. */} diff --git a/src/lib/feedFilterUtils.ts b/src/lib/feedFilterUtils.ts index f8f63018..2d073873 100644 --- a/src/lib/feedFilterUtils.ts +++ b/src/lib/feedFilterUtils.ts @@ -10,9 +10,54 @@ type KindOption = { icon: React.ComponentType<{ className?: string }> | undefined; }; -/** Build the kind options from EXTRA_KINDS definitions. */ +/** + * Agora-native kinds that are not modeled in EXTRA_KINDS (which drives the + * sidebar / feed-settings UI). We surface them in the search Kind picker + * because they are first-class searchable content on Agora. + */ +const AGORA_NATIVE_KIND_OPTIONS: KindOption[] = [ + { + value: '33863', + label: 'Campaigns (33863)', + description: 'Fundraising campaigns', + parentId: 'campaigns', + icon: CONTENT_KIND_ICONS['campaigns'], + }, + { + value: '36639', + label: 'Pledges (36639)', + description: 'Donor pledges for concrete actions', + parentId: 'actions', + icon: CONTENT_KIND_ICONS['actions'], + }, +]; + +/** + * Agora's curated "main content" kinds, surfaced as a preset section at the + * top of the KindPicker. Order is the order they appear in the picker. + * + * Includes Agora-native kinds plus the existing-NIP kinds Agora foregrounds + * (campaigns, pledges, communities, posts, articles, events, polls, photos, + * videos). Excludes social-signal kinds (reactions, reposts, zaps) and + * stats snapshots, which users rarely filter on directly. + */ +export const AGORA_PRESET_KIND_VALUES: readonly string[] = [ + '33863', // Campaigns + '36639', // Pledges + '34550', // Communities + '1', // Posts + '30023', // Articles + '31923', // Events (time) + '1068', // Polls + '20', // Photos + '21', // Videos +] as const; + +/** Build the kind options from EXTRA_KINDS definitions plus Agora-native kinds. */ export function buildKindOptions(): KindOption[] { const options: KindOption[] = []; + // Agora-native kinds appear first so they're easy to find. + for (const opt of AGORA_NATIVE_KIND_OPTIONS) options.push(opt); for (const def of EXTRA_KINDS) { if (def.subKinds) { for (const sub of def.subKinds) {