diff --git a/frontend/src/components/AppShell.tsx b/frontend/src/components/AppShell.tsx
index 558e3e1..72b9cb0 100644
--- a/frontend/src/components/AppShell.tsx
+++ b/frontend/src/components/AppShell.tsx
@@ -9,6 +9,7 @@ import { ChannelInfoPane } from './ChannelInfoPane';
import { Toaster } from './ui/sonner';
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from './ui/sheet';
import {
+ SETTINGS_SECTION_ICONS,
SETTINGS_SECTION_LABELS,
SETTINGS_SECTION_ORDER,
type SettingsSection,
@@ -115,20 +116,26 @@ export function AppShell({
- {SETTINGS_SECTION_ORDER.map((section) => (
-
- ))}
+ {SETTINGS_SECTION_ORDER.map((section) => {
+ const Icon = SETTINGS_SECTION_ICONS[section];
+ return (
+
+ );
+ })}
);
@@ -216,7 +223,13 @@ export function AppShell({
Radio & Settings
- {SETTINGS_SECTION_LABELS[settingsSection]}
+
+ {(() => {
+ const Icon = SETTINGS_SECTION_ICONS[settingsSection];
+ return ;
+ })()}
+ {SETTINGS_SECTION_LABELS[settingsSection]}
+
diff --git a/frontend/src/components/ChannelInfoPane.tsx b/frontend/src/components/ChannelInfoPane.tsx
index 26eeb6d..7c284bd 100644
--- a/frontend/src/components/ChannelInfoPane.tsx
+++ b/frontend/src/components/ChannelInfoPane.tsx
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
+import { Star } from 'lucide-react';
import { api } from '../api';
import { formatTime } from '../utils/messageParser';
import { isFavorite } from '../utils/favorites';
@@ -125,12 +126,12 @@ export function ChannelInfoPane({
>
{isFavorite(favorites, 'channel', channel.key) ? (
<>
-
★
+
Remove from favorites
>
) : (
<>
-
☆
+
Add to favorites
>
)}
diff --git a/frontend/src/components/ChatHeader.tsx b/frontend/src/components/ChatHeader.tsx
index cdd9341..1537bec 100644
--- a/frontend/src/components/ChatHeader.tsx
+++ b/frontend/src/components/ChatHeader.tsx
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
+import { Globe2, Info, Route, Star, Trash2 } from 'lucide-react';
import { toast } from './ui/sonner';
import { isFavorite } from '../utils/favorites';
import { handleKeyboardActivate } from '../utils/a11y';
@@ -71,8 +72,8 @@ export function ChatHeader({
};
return (
-
-
+
+
{conversation.type === 'contact' && onOpenContactInfo && (
)}
- {
- if (conversation.type === 'contact' && onOpenContactInfo) {
- onOpenContactInfo(conversation.id);
- } else if (conversation.type === 'channel' && onOpenChannelInfo) {
- onOpenChannelInfo(conversation.id);
- }
+
+
+
+ {
+ if (conversation.type === 'contact' && onOpenContactInfo) {
+ onOpenContactInfo(conversation.id);
+ } else if (conversation.type === 'channel' && onOpenChannelInfo) {
+ onOpenChannelInfo(conversation.id);
+ }
+ }
+ : undefined
}
- : undefined
- }
- >
- {conversation.type === 'channel' &&
- !conversation.name.startsWith('#') &&
- activeChannel?.is_hashtag
- ? '#'
- : ''}
- {conversation.name}
-
- {isPrivateChannel && !showKey ? (
-
- ) : (
- {
- e.stopPropagation();
- navigator.clipboard.writeText(conversation.id);
- toast.success(
- conversation.type === 'channel' ? 'Room key copied!' : 'Contact key copied!'
- );
- }}
- title="Click to copy"
- aria-label={conversation.type === 'channel' ? 'Copy channel key' : 'Copy contact key'}
- >
- {conversation.type === 'channel' ? conversation.id.toLowerCase() : conversation.id}
+ >
+
+ {conversation.type === 'channel' &&
+ !conversation.name.startsWith('#') &&
+ activeChannel?.is_hashtag
+ ? '#'
+ : ''}
+ {conversation.name}
+
+ {titleClickable && (
+
+ )}
+
+ {isPrivateChannel && !showKey ? (
+
+ ) : (
+ {
+ e.stopPropagation();
+ navigator.clipboard.writeText(conversation.id);
+ toast.success(
+ conversation.type === 'channel' ? 'Room key copied!' : 'Contact key copied!'
+ );
+ }}
+ title="Click to copy"
+ aria-label={
+ conversation.type === 'channel' ? 'Copy channel key' : 'Copy contact key'
+ }
+ >
+ {conversation.type === 'channel'
+ ? conversation.id.toLowerCase()
+ : conversation.id}
+
+ )}
+
+ {conversation.type === 'channel' && activeChannel?.flood_scope_override && (
+
+ Regional override active:{' '}
+ {stripRegionScopePrefix(activeChannel.flood_scope_override)}
+
+ )}
+ {conversation.type === 'contact' &&
+ (() => {
+ const contact = contacts.find((c) => c.public_key === conversation.id);
+ if (!contact) return null;
+ return (
+
+
+
+ );
+ })()}
- )}
- {conversation.type === 'channel' && activeChannel?.flood_scope_override && (
-
- Regional override active: {stripRegionScopePrefix(activeChannel.flood_scope_override)}
-
- )}
- {conversation.type === 'contact' &&
- (() => {
- const contact = contacts.find((c) => c.public_key === conversation.id);
- if (!contact) return null;
- return (
-
- );
- })()}
+
{conversation.type === 'contact' && (
)}
{conversation.type === 'channel' && onSetChannelFloodScopeOverride && (
)}
{(conversation.type === 'channel' || conversation.type === 'contact') && (
)}
{!(conversation.type === 'channel' && conversation.name === 'Public') && (
)}
diff --git a/frontend/src/components/ContactInfoPane.tsx b/frontend/src/components/ContactInfoPane.tsx
index ef0a26c..3ec486a 100644
--- a/frontend/src/components/ContactInfoPane.tsx
+++ b/frontend/src/components/ContactInfoPane.tsx
@@ -1,4 +1,5 @@
import { type ReactNode, useEffect, useState } from 'react';
+import { Ban, Star } from 'lucide-react';
import { api } from '../api';
import { formatTime } from '../utils/messageParser';
import {
@@ -152,12 +153,12 @@ export function ContactInfoPane({
>
{blockedNames.includes(nameOnlyValue) ? (
<>
- ✘
+
Unblock this name
>
) : (
<>
- ✘
+
Block this name
>
)}
@@ -283,12 +284,12 @@ export function ContactInfoPane({
>
{isFavorite(favorites, 'contact', contact.public_key) ? (
<>
- ★
+
Remove from favorites
>
) : (
<>
- ☆
+
Add to favorites
>
)}
@@ -306,12 +307,12 @@ export function ContactInfoPane({
>
{blockedKeys.includes(contact.public_key.toLowerCase()) ? (
<>
- ✘
+
Unblock this key
>
) : (
<>
- ✘
+
Block this key
>
)}
@@ -325,12 +326,12 @@ export function ContactInfoPane({
>
{blockedNames.includes(contact.name) ? (
<>
- ✘
+
Unblock name “{contact.name}”
>
) : (
<>
- ✘
+
Block name “{contact.name}”
>
)}
diff --git a/frontend/src/components/NewMessageModal.tsx b/frontend/src/components/NewMessageModal.tsx
index 4417633..246764a 100644
--- a/frontend/src/components/NewMessageModal.tsx
+++ b/frontend/src/components/NewMessageModal.tsx
@@ -1,4 +1,5 @@
import { useState, useRef } from 'react';
+import { Dice5 } from 'lucide-react';
import type { Contact, Conversation } from '../types';
import { getContactDisplayName } from '../utils/pubkey';
import {
@@ -256,7 +257,7 @@ export function NewMessageModal({
title="Generate random key"
aria-label="Generate random key"
>
- π²
+
diff --git a/frontend/src/components/RepeaterDashboard.tsx b/frontend/src/components/RepeaterDashboard.tsx
index f118aa2..1982b68 100644
--- a/frontend/src/components/RepeaterDashboard.tsx
+++ b/frontend/src/components/RepeaterDashboard.tsx
@@ -1,5 +1,6 @@
import { toast } from './ui/sonner';
import { Button } from './ui/button';
+import { Route, Star, Trash2 } from 'lucide-react';
import { RepeaterLogin } from './RepeaterLogin';
import { useRepeaterDashboard } from '../hooks/useRepeaterDashboard';
import { isFavorite } from '../utils/favorites';
@@ -71,23 +72,33 @@ export function RepeaterDashboard({
return (
{/* Header */}
-
-
- {conversation.name}
- {
- navigator.clipboard.writeText(conversation.id);
- toast.success('Contact key copied!');
- }}
- title="Click to copy"
- >
- {conversation.id}
+
+
+
+
+
+ {conversation.name}
+
+ {
+ navigator.clipboard.writeText(conversation.id);
+ toast.success('Contact key copied!');
+ }}
+ title="Click to copy"
+ >
+ {conversation.id}
+
+
+ {contact && (
+
+
+
+ )}
- {contact && }
{loggedIn && (
@@ -102,15 +113,15 @@ export function RepeaterDashboard({
)}
diff --git a/frontend/src/components/SettingsModal.tsx b/frontend/src/components/SettingsModal.tsx
index 46c62be..e3a2a78 100644
--- a/frontend/src/components/SettingsModal.tsx
+++ b/frontend/src/components/SettingsModal.tsx
@@ -7,7 +7,11 @@ import type {
RadioConfigUpdate,
} from '../types';
import type { LocalLabel } from '../utils/localLabel';
-import { SETTINGS_SECTION_LABELS, type SettingsSection } from './settings/settingsConstants';
+import {
+ SETTINGS_SECTION_ICONS,
+ SETTINGS_SECTION_LABELS,
+ type SettingsSection,
+} from './settings/settingsConstants';
import { SettingsRadioSection } from './settings/SettingsRadioSection';
import { SettingsLocalSection } from './settings/SettingsLocalSection';
@@ -138,6 +142,7 @@ export function SettingsModal(props: SettingsModalProps) {
const renderSectionHeader = (section: SettingsSection): ReactNode => {
if (!showSectionButton) return null;
+ const Icon = SETTINGS_SECTION_ICONS[section];
return (
{(showSortToggle || unreadCount > 0) && (
@@ -651,7 +665,7 @@ export function Sidebar({
aria-label="New message"
className="h-6 w-6 p-0 text-muted-foreground hover:text-foreground transition-colors"
>
- +
+
@@ -672,7 +686,7 @@ export function Sidebar({
title="Clear search"
aria-label="Clear search"
>
- Γ
+
)}
@@ -696,9 +710,7 @@ export function Sidebar({
onKeyDown={handleKeyboardActivate}
onClick={onMarkAllRead}
>
-
- β
-
+
Mark all as read
)}
diff --git a/frontend/src/components/StatusBar.tsx b/frontend/src/components/StatusBar.tsx
index 037fabf..1b5c97e 100644
--- a/frontend/src/components/StatusBar.tsx
+++ b/frontend/src/components/StatusBar.tsx
@@ -52,16 +52,16 @@ export function StatusBar({
{onMenuClick && (
)}