diff --git a/src/components/FeedSettingsForm.tsx b/src/components/FeedSettingsForm.tsx deleted file mode 100644 index dc792b00..00000000 --- a/src/components/FeedSettingsForm.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import { Clapperboard, BarChart3, Palette, PartyPopper, Radio, MessageSquare, Repeat2, FileText } from 'lucide-react'; -import { ChestIcon } from '@/components/icons/ChestIcon'; -import { CardsIcon } from '@/components/icons/CardsIcon'; -import { Switch } from '@/components/ui/switch'; -import { useFeedSettings } from '@/hooks/useFeedSettings'; -import { useEncryptedSettings } from '@/hooks/useEncryptedSettings'; -import { useCurrentUser } from '@/hooks/useCurrentUser'; -import { EXTRA_KINDS, SECTION_ORDER, SECTION_LABELS } from '@/lib/extraKinds'; -import type { ExtraKindDef, SubKindDef } from '@/lib/extraKinds'; -import { cn } from '@/lib/utils'; - -/** Map route name or kind → lucide icon. */ -const ICONS: Record = { - vines: , - polls: , - treasures: , - colors: , - packs: , - streams: , - articles: , - decks: , - // Feed-only items (keyed by kind number) - '1': , - '6': , -}; - -function KindBadge({ kind }: { kind: number }) { - return ( - - [{kind}] - - ); -} - -function SubKindRow({ sub, parentEnabled }: { sub: SubKindDef; parentEnabled: boolean }) { - const { feedSettings, updateFeedSettings } = useFeedSettings(); - const { updateSettings } = useEncryptedSettings(); - const { user } = useCurrentUser(); - - const handleToggle = async (key: string, value: boolean) => { - // Update local settings immediately - updateFeedSettings({ [key]: value }); - - // Sync to encrypted storage if logged in - if (user) { - const updatedFeedSettings = { ...feedSettings, [key]: value }; - await updateSettings.mutateAsync({ feedSettings: updatedFeedSettings }); - } - }; - - return ( -
-
- {sub.label} -

- {' '}{sub.description} -

-
-
-
- handleToggle(sub.showKey, checked)} - className="scale-90" - /> -
-
- handleToggle(sub.feedKey, checked)} - className="scale-90" - /> -
-
-
- ); -} - -function ContentTypeRow({ def }: { def: ExtraKindDef }) { - const { feedSettings, updateFeedSettings } = useFeedSettings(); - const { updateSettings } = useEncryptedSettings(); - const { user } = useCurrentUser(); - const icon = ICONS[def.route ?? String(def.kind)] ?? ; - const hasSubKinds = !!def.subKinds; - const isFeedOnly = !!def.feedOnly; - - const handleToggle = async (key: string, value: boolean) => { - // Update local settings immediately - updateFeedSettings({ [key]: value }); - - // Sync to encrypted storage if logged in - if (user) { - const updatedFeedSettings = { ...feedSettings, [key]: value }; - await updateSettings.mutateAsync({ feedSettings: updatedFeedSettings }); - } - }; - - return ( -
-
-
- {icon} -
- {def.label} -

- {' '}{def.description} -

-
-
-
- {isFeedOnly ? ( - /* Feed-only items: single centered toggle spanning both columns */ - <> -
-
- {def.feedKey ? ( - handleToggle(def.feedKey!, checked)} - /> - ) : null} -
- - ) : ( - /* Regular items: sidebar + feed toggles */ - <> -
- {def.showKey ? ( - handleToggle(def.showKey!, checked)} - /> - ) : null} -
-
- {!hasSubKinds && def.feedKey ? ( - handleToggle(def.feedKey!, checked)} - /> - ) : null} -
- - )} -
-
- - {/* Sub-kind toggles */} - {hasSubKinds && def.showKey && def.subKinds!.map((sub) => ( - - ))} -
- ); -} - -export function FeedSettingsForm() { - return ( -
- {/* Intro */} -
- -
-

Other Stuff

-

- Nostr isn't just text posts — people publish all kinds of things. Pick what shows up in your sidebar and feed. -

-
-
- - {/* Column headers */} -
- Sidebar - Feed -
- - {/* Content type rows grouped by section */} - {SECTION_ORDER.map((section) => { - const sectionKinds = EXTRA_KINDS.filter((def) => def.section === section); - if (sectionKinds.length === 0) return null; - return ( -
-
- - {SECTION_LABELS[section]} - -
- {sectionKinds.map((def) => ( - - ))} -
- ); - })} -
- ); -} diff --git a/src/components/MuteSettings.tsx b/src/components/MuteSettings.tsx deleted file mode 100644 index b18b3720..00000000 --- a/src/components/MuteSettings.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import { useState } from 'react'; -import { Trash2, Plus, UserX, Hash, MessageSquareOff } from 'lucide-react'; - -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Skeleton } from '@/components/ui/skeleton'; -import { useToast } from '@/hooks/useToast'; -import { useMuteList, type MuteListItem } from '@/hooks/useMuteList'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; - -const MUTE_TYPE_CONFIG = { - pubkey: { - icon: , - label: 'Users', - description: 'Hide posts from specific users', - inputLabel: 'Public Key (hex or npub)', - placeholder: 'npub1... or hex pubkey', - }, - hashtag: { - icon: , - label: 'Hashtags', - description: 'Hide posts with specific hashtags', - inputLabel: 'Hashtag (without #)', - placeholder: 'bitcoin', - }, - word: { - icon: , - label: 'Words', - description: 'Hide posts containing specific words or phrases', - inputLabel: 'Word or Phrase', - placeholder: 'spam word', - }, - thread: { - icon: , - label: 'Threads', - description: 'Hide entire conversation threads', - inputLabel: 'Event ID (hex or note)', - placeholder: 'note1... or hex event ID', - }, -}; - -export function MuteSettings() { - const { muteItems, isLoading, addMute, removeMute } = useMuteList(); - const { toast } = useToast(); - const [newMuteType, setNewMuteType] = useState('pubkey'); - const [newMuteValue, setNewMuteValue] = useState(''); - - const handleAddMute = async () => { - if (!newMuteValue.trim()) { - toast({ title: 'Error', description: 'Please enter a value', variant: 'destructive' }); - return; - } - - try { - await addMute.mutateAsync({ - type: newMuteType, - value: newMuteValue.trim(), - }); - - toast({ title: 'Success', description: 'Mute added successfully' }); - setNewMuteValue(''); - } catch (error) { - toast({ - title: 'Error', - description: error instanceof Error ? error.message : 'Failed to add mute', - variant: 'destructive', - }); - } - }; - - const handleRemoveMute = async (item: MuteListItem) => { - try { - await removeMute.mutateAsync(item); - toast({ title: 'Success', description: 'Mute removed successfully' }); - } catch (error) { - toast({ - title: 'Error', - description: error instanceof Error ? error.message : 'Failed to remove mute', - variant: 'destructive', - }); - } - }; - - const groupedMutes = { - pubkey: muteItems.filter((item) => item.type === 'pubkey'), - hashtag: muteItems.filter((item) => item.type === 'hashtag'), - word: muteItems.filter((item) => item.type === 'word'), - thread: muteItems.filter((item) => item.type === 'thread'), - }; - - return ( -
- {/* Intro */} -
- -
-

Content Control

-

- Hide posts from specific users, hashtags, words, or entire threads. All mutes are encrypted and private. -

-
-
- - {/* Add mute section */} -
-
-
-
- - -
- -
- - setNewMuteValue(e.target.value)} - placeholder={MUTE_TYPE_CONFIG[newMuteType].placeholder} - className="h-9" - /> -
-
- - -
-
- - {/* Muted items list */} - {isLoading ? ( -
- - - -
- ) : muteItems.length === 0 ? ( -

- No muted items yet -

- ) : ( - <> - {Object.entries(groupedMutes).map(([type, items]) => { - if (items.length === 0) return null; - const config = MUTE_TYPE_CONFIG[type as MuteListItem['type']]; - - return ( - - ); - })} - - )} -
- ); -} - -function MuteTypeSection({ - type: _type, - config, - items, - onRemove, - isPending, -}: { - type: MuteListItem['type']; - config: typeof MUTE_TYPE_CONFIG[keyof typeof MUTE_TYPE_CONFIG]; - items: MuteListItem[]; - onRemove: (item: MuteListItem) => void; - isPending: boolean; -}) { - return ( -
-
- {config.icon} -
- {config.label} -

- {items.length} {items.length === 1 ? 'item' : 'items'} • {config.description} -

-
-
- -
- {items.map((item, index) => ( -
-
- - {item.value} - -
- -
- ))} -
-
- ); -} diff --git a/src/hooks/useProfileFollowing.ts b/src/hooks/useProfileFollowing.ts deleted file mode 100644 index 60e1b1f5..00000000 --- a/src/hooks/useProfileFollowing.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useNostr } from '@nostrify/react'; -import { useQuery } from '@tanstack/react-query'; - -/** - * Hook to fetch a user's follow list (kind 3) and extract the pubkeys they follow. - * Works for any pubkey, not just the logged-in user. - */ -export function useProfileFollowing(pubkey: string | undefined) { - const { nostr } = useNostr(); - - return useQuery({ - queryKey: ['profile-following', pubkey], - queryFn: async ({ signal }) => { - if (!pubkey) return { pubkeys: [], count: 0 }; - const [event] = await nostr.query( - [{ kinds: [3], authors: [pubkey], limit: 1 }], - { signal: AbortSignal.any([signal, AbortSignal.timeout(5000)]) }, - ); - if (!event) return { pubkeys: [], count: 0 }; - const pubkeys = event.tags - .filter(([name]) => name === 'p') - .map(([, pk]) => pk); - return { pubkeys, count: pubkeys.length }; - }, - enabled: !!pubkey, - staleTime: 5 * 60 * 1000, - }); -}