Add encrypted notification tracking with visual indicators

- Add notificationsCursor to EncryptedSettings interface
- Create useNotifications hook to track read/unread status
- Update NotificationsPage to mark as read after 1 second
- Highlight new notifications with accent background and left border
- Add dot indicator to Bell icon in sidebar and mobile nav when unread
- Auto-mark notifications as read when viewing the page
- Sync notification cursor across devices via NIP-78

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy
2026-02-18 20:54:01 -06:00
parent fd2232f671
commit e7e4a1fa5c
5 changed files with 149 additions and 37 deletions
+9 -2
View File
@@ -15,6 +15,7 @@ import { useLoggedInAccounts, type Account } from '@/hooks/useLoggedInAccounts';
import { useLoginActions } from '@/hooks/useLoginActions';
import { useTheme } from '@/hooks/useTheme';
import { useFeedSettings } from '@/hooks/useFeedSettings';
import { useNotifications } from '@/hooks/useNotifications';
import { EXTRA_KINDS } from '@/lib/extraKinds';
import { genUserName } from '@/lib/genUserName';
import { cn } from '@/lib/utils';
@@ -25,19 +26,23 @@ interface NavItemProps {
icon: React.ReactNode;
label: string;
active?: boolean;
showIndicator?: boolean;
}
function NavItem({ to, icon, label, active }: NavItemProps) {
function NavItem({ to, icon, label, active, showIndicator }: NavItemProps) {
return (
<Link
to={to}
className={cn(
'flex items-center gap-4 px-4 py-3 rounded-full transition-colors text-lg hover:bg-secondary/60',
'flex items-center gap-4 px-4 py-3 rounded-full transition-colors text-lg hover:bg-secondary/60 relative',
active ? 'font-bold' : 'font-normal text-muted-foreground',
)}
>
{icon}
<span>{label}</span>
{showIndicator && (
<span className="absolute right-4 size-2 bg-primary rounded-full" />
)}
</Link>
);
}
@@ -50,6 +55,7 @@ export function LeftSidebar() {
const { logout } = useLoginActions();
const { theme, setTheme } = useTheme();
const { feedSettings } = useFeedSettings();
const { hasUnread } = useNotifications();
const [loginDialogOpen, setLoginDialogOpen] = useState(false);
const [signupDialogOpen, setSignupDialogOpen] = useState(false);
const [accountPopoverOpen, setAccountPopoverOpen] = useState(false);
@@ -138,6 +144,7 @@ export function LeftSidebar() {
icon={item.icon}
label={item.label}
active={location.pathname === item.to}
showIndicator={item.to === '/notifications' && hasUnread}
/>
))}
+9 -2
View File
@@ -1,31 +1,37 @@
import { Link, useLocation } from 'react-router-dom';
import { Home, Bell, Search } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useNotifications } from '@/hooks/useNotifications';
interface NavTabProps {
to: string;
icon: React.ReactNode;
label: string;
active: boolean;
showIndicator?: boolean;
}
function NavTab({ to, icon, label, active }: NavTabProps) {
function NavTab({ to, icon, label, active, showIndicator }: NavTabProps) {
return (
<Link
to={to}
className={cn(
'flex flex-col items-center justify-center gap-0.5 flex-1 py-2 transition-colors',
'flex flex-col items-center justify-center gap-0.5 flex-1 py-2 transition-colors relative',
active ? 'text-foreground' : 'text-muted-foreground',
)}
>
{icon}
<span className="text-[10px] font-medium">{label}</span>
{showIndicator && (
<span className="absolute top-1.5 left-1/2 translate-x-1 size-2 bg-primary rounded-full" />
)}
</Link>
);
}
export function MobileBottomNav() {
const location = useLocation();
const { hasUnread } = useNotifications();
return (
<nav className="fixed bottom-0 left-0 right-0 z-20 flex items-center bg-background/95 backdrop-blur-md border-t border-border sidebar:hidden safe-area-bottom">
@@ -40,6 +46,7 @@ export function MobileBottomNav() {
icon={<Bell className="size-5" />}
label="Notifications"
active={location.pathname === '/notifications'}
showIndicator={hasUnread}
/>
<NavTab
to="/search"