Add favorites as contact field (dug)

This commit is contained in:
Jack Kingsman
2026-04-05 20:50:27 -07:00
parent c2e1a3cbe6
commit a351c86ccb
55 changed files with 482 additions and 466 deletions
+2 -5
View File
@@ -3,17 +3,15 @@ import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip as RechartsTooltip }
import { Star } from 'lucide-react';
import { api } from '../api';
import { formatTime } from '../utils/messageParser';
import { isFavorite } from '../utils/favorites';
import { handleKeyboardActivate } from '../utils/a11y';
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from './ui/sheet';
import { toast } from './ui/sonner';
import type { Channel, ChannelDetail, Favorite, PathHashWidthStats } from '../types';
import type { Channel, ChannelDetail, PathHashWidthStats } from '../types';
interface ChannelInfoPaneProps {
channelKey: string | null;
onClose: () => void;
channels: Channel[];
favorites: Favorite[];
onToggleFavorite: (type: 'channel' | 'contact', id: string) => void;
}
@@ -21,7 +19,6 @@ export function ChannelInfoPane({
channelKey,
onClose,
channels,
favorites,
onToggleFavorite,
}: ChannelInfoPaneProps) {
const [detail, setDetail] = useState<ChannelDetail | null>(null);
@@ -125,7 +122,7 @@ export function ChannelInfoPane({
className="text-sm flex items-center gap-2 hover:text-primary transition-colors"
onClick={() => onToggleFavorite('channel', channel.key)}
>
{isFavorite(favorites, 'channel', channel.key) ? (
{channel.favorite ? (
<>
<Star className="h-4.5 w-4.5 fill-current text-favorite" aria-hidden="true" />
<span>Remove from favorites</span>
+11 -19
View File
@@ -5,7 +5,6 @@ import { DirectTraceIcon } from './DirectTraceIcon';
import { ContactPathDiscoveryModal } from './ContactPathDiscoveryModal';
import { ChannelFloodScopeOverrideModal } from './ChannelFloodScopeOverrideModal';
import { ChannelPathHashModeOverrideModal } from './ChannelPathHashModeOverrideModal';
import { isFavorite } from '../utils/favorites';
import { handleKeyboardActivate } from '../utils/a11y';
import { isPublicChannelKey } from '../utils/publicChannel';
import { stripRegionScopePrefix } from '../utils/regionScope';
@@ -13,14 +12,7 @@ import { isPrefixOnlyContact } from '../utils/pubkey';
import { cn } from '../lib/utils';
import { ContactAvatar } from './ContactAvatar';
import { ContactStatusInfo } from './ContactStatusInfo';
import type {
Channel,
Contact,
Conversation,
Favorite,
PathDiscoveryResponse,
RadioConfig,
} from '../types';
import type { Channel, Contact, Conversation, PathDiscoveryResponse, RadioConfig } from '../types';
import { CONTACT_TYPE_ROOM } from '../types';
interface ChatHeaderProps {
@@ -28,7 +20,6 @@ interface ChatHeaderProps {
contacts: Contact[];
channels: Channel[];
config: RadioConfig | null;
favorites: Favorite[];
notificationsSupported: boolean;
notificationsEnabled: boolean;
notificationsPermission: NotificationPermission | 'unsupported';
@@ -49,7 +40,6 @@ export function ChatHeader({
contacts,
channels,
config,
favorites,
notificationsSupported,
notificationsEnabled,
notificationsPermission,
@@ -105,12 +95,18 @@ export function ChatHeader({
const titleClickable =
(conversation.type === 'contact' && onOpenContactInfo) ||
(conversation.type === 'channel' && onOpenChannelInfo);
const isFav =
conversation.type === 'contact'
? (activeContact?.favorite ?? false)
: conversation.type === 'channel'
? (activeChannel?.favorite ?? false)
: false;
const favoriteTitle =
conversation.type === 'contact'
? isFavorite(favorites, 'contact', conversation.id)
? isFav
? 'Remove from favorites. Favorite contacts stay loaded on the radio for ACK support.'
: 'Add to favorites. Favorite contacts stay loaded on the radio for ACK support.'
: isFavorite(favorites, conversation.type as 'channel' | 'contact', conversation.id)
: isFav
? 'Remove from favorites'
: 'Add to favorites';
@@ -359,13 +355,9 @@ export function ChatHeader({
onToggleFavorite(conversation.type as 'channel' | 'contact', conversation.id)
}
title={favoriteTitle}
aria-label={
isFavorite(favorites, conversation.type as 'channel' | 'contact', conversation.id)
? 'Remove from favorites'
: 'Add to favorites'
}
aria-label={isFav ? 'Remove from favorites' : 'Add to favorites'}
>
{isFavorite(favorites, conversation.type as 'channel' | 'contact', conversation.id) ? (
{isFav ? (
<Star className="h-4 w-4 fill-current text-favorite" aria-hidden="true" />
) : (
<Star className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
+1 -5
View File
@@ -29,7 +29,6 @@ import {
} from '../utils/pathUtils';
import { isPublicChannelKey } from '../utils/publicChannel';
import { getMapFocusHash } from '../utils/urlHash';
import { isFavorite } from '../utils/favorites';
import { handleKeyboardActivate } from '../utils/a11y';
import { ContactAvatar } from './ContactAvatar';
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from './ui/sheet';
@@ -42,7 +41,6 @@ import type {
ContactAnalytics,
ContactAnalyticsHourlyBucket,
ContactAnalyticsWeeklyBucket,
Favorite,
RadioConfig,
} from '../types';
@@ -67,7 +65,6 @@ interface ContactInfoPaneProps {
onClose: () => void;
contacts: Contact[];
config: RadioConfig | null;
favorites: Favorite[];
onToggleFavorite: (type: 'channel' | 'contact', id: string) => void;
onNavigateToChannel?: (channelKey: string) => void;
onSearchMessagesByKey?: (publicKey: string) => void;
@@ -84,7 +81,6 @@ export function ContactInfoPane({
onClose,
contacts,
config,
favorites,
onToggleFavorite,
onNavigateToChannel,
onSearchMessagesByKey,
@@ -384,7 +380,7 @@ export function ContactInfoPane({
onClick={() => onToggleFavorite('contact', contact.public_key)}
title="Favorite contacts stay loaded on the radio for ACK support"
>
{isFavorite(favorites, 'contact', contact.public_key) ? (
{contact.favorite ? (
<>
<Star className="h-4.5 w-4.5 fill-current text-favorite" aria-hidden="true" />
<span>Remove from favorites</span>
@@ -10,7 +10,6 @@ import type {
Channel,
Contact,
Conversation,
Favorite,
HealthStatus,
Message,
PathDiscoveryResponse,
@@ -42,7 +41,6 @@ interface ConversationPaneProps {
notificationsSupported: boolean;
notificationsEnabled: boolean;
notificationsPermission: NotificationPermission | 'unsupported';
favorites: Favorite[];
messages: Message[];
preSorted?: boolean;
messagesLoading: boolean;
@@ -119,7 +117,6 @@ export function ConversationPane({
notificationsSupported,
notificationsEnabled,
notificationsPermission,
favorites,
messages,
preSorted,
messagesLoading,
@@ -237,7 +234,6 @@ export function ConversationPane({
key={activeConversation.id}
conversation={activeConversation}
contacts={contacts}
favorites={favorites}
notificationsSupported={notificationsSupported}
notificationsEnabled={notificationsEnabled}
notificationsPermission={notificationsPermission}
@@ -266,7 +262,6 @@ export function ConversationPane({
contacts={contacts}
channels={channels}
config={config}
favorites={favorites}
notificationsSupported={notificationsSupported}
notificationsEnabled={notificationsEnabled}
notificationsPermission={notificationsPermission}
+2 -11
View File
@@ -9,17 +9,10 @@ import { RepeaterLogin } from './RepeaterLogin';
import { ServerLoginStatusBanner } from './ServerLoginStatusBanner';
import { useRememberedServerPassword } from '../hooks/useRememberedServerPassword';
import { useRepeaterDashboard } from '../hooks/useRepeaterDashboard';
import { isFavorite } from '../utils/favorites';
import { handleKeyboardActivate } from '../utils/a11y';
import { isValidLocation } from '../utils/pathUtils';
import { ContactStatusInfo } from './ContactStatusInfo';
import type {
Contact,
Conversation,
Favorite,
PathDiscoveryResponse,
TelemetryHistoryEntry,
} from '../types';
import type { Contact, Conversation, PathDiscoveryResponse, TelemetryHistoryEntry } from '../types';
import { cn } from '../lib/utils';
import { TelemetryPane } from './repeater/RepeaterTelemetryPane';
import { NeighborsPane } from './repeater/RepeaterNeighborsPane';
@@ -41,7 +34,6 @@ export { formatDuration, formatClockDrift } from './repeater/repeaterPaneShared'
interface RepeaterDashboardProps {
conversation: Conversation;
contacts: Contact[];
favorites: Favorite[];
notificationsSupported: boolean;
notificationsEnabled: boolean;
notificationsPermission: NotificationPermission | 'unsupported';
@@ -61,7 +53,6 @@ interface RepeaterDashboardProps {
export function RepeaterDashboard({
conversation,
contacts,
favorites,
notificationsSupported,
notificationsEnabled,
notificationsPermission,
@@ -134,7 +125,7 @@ export function RepeaterDashboard({
setTelemetryHistory(liveHistory);
}, [paneData.status?.telemetry_history]);
const isFav = isFavorite(favorites, 'contact', conversation.id);
const isFav = contact?.favorite ?? false;
const handleRepeaterLogin = async (nextPassword: string) => {
await login(nextPassword);
+6 -17
View File
@@ -19,7 +19,6 @@ import {
type Contact,
type Channel,
type Conversation,
type Favorite,
} from '../types';
import {
buildSidebarSectionSortOrders,
@@ -36,7 +35,6 @@ import { isPublicChannelKey } from '../utils/publicChannel';
import { getContactDisplayName } from '../utils/pubkey';
import { handleKeyboardActivate } from '../utils/a11y';
import { ContactAvatar } from './ContactAvatar';
import { isFavorite } from '../utils/favorites';
import { Input } from './ui/input';
import { Button } from './ui/button';
import { cn } from '@/lib/utils';
@@ -106,7 +104,6 @@ interface SidebarProps {
crackerRunning: boolean;
onToggleCracker: () => void;
onMarkAllRead: () => void;
favorites: Favorite[];
isConversationNotificationsEnabled?: (type: 'channel' | 'contact', id: string) => boolean;
blockedKeys?: string[];
blockedNames?: string[];
@@ -135,7 +132,6 @@ export function Sidebar({
crackerRunning,
onToggleCracker,
onMarkAllRead,
favorites,
isConversationNotificationsEnabled,
blockedKeys = [],
blockedNames = [],
@@ -488,22 +484,16 @@ export function Sidebar({
nonFavoriteRooms,
nonFavoriteRepeaters,
} = useMemo(() => {
const favChannels = filteredChannels.filter((c) => isFavorite(favorites, 'channel', c.key));
const favChannels = filteredChannels.filter((c) => c.favorite);
const favContacts = [
...filteredNonRepeaterContacts,
...filteredRooms,
...filteredRepeaters,
].filter((c) => isFavorite(favorites, 'contact', c.public_key));
const nonFavChannels = filteredChannels.filter((c) => !isFavorite(favorites, 'channel', c.key));
const nonFavContacts = filteredNonRepeaterContacts.filter(
(c) => !isFavorite(favorites, 'contact', c.public_key)
);
const nonFavRooms = filteredRooms.filter(
(c) => !isFavorite(favorites, 'contact', c.public_key)
);
const nonFavRepeaters = filteredRepeaters.filter(
(c) => !isFavorite(favorites, 'contact', c.public_key)
);
].filter((c) => c.favorite);
const nonFavChannels = filteredChannels.filter((c) => !c.favorite);
const nonFavContacts = filteredNonRepeaterContacts.filter((c) => !c.favorite);
const nonFavRooms = filteredRooms.filter((c) => !c.favorite);
const nonFavRepeaters = filteredRepeaters.filter((c) => !c.favorite);
const items: FavoriteItem[] = [
...favChannels.map((channel) => ({ type: 'channel' as const, channel })),
@@ -522,7 +512,6 @@ export function Sidebar({
filteredNonRepeaterContacts,
filteredRooms,
filteredRepeaters,
favorites,
sectionSortOrders.favorites,
sortFavoriteItemsByOrder,
]);