Public Access
feat(ui): add follow button, follows card, and inbox page
This commit is contained in:
1 parent
3aed243e35
commit
43c4862606
23 files changed
+603
-2
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import {useEffect, useState} from 'react';
|
||||
import {Link, NavLink, useLocation, useNavigate} from 'react-router-dom';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {ChartColumn, Coins, Dices, Eye, Languages, LayoutDashboard, Library, Link2, LogOut, Mail, Megaphone, Moon, Newspaper, ScrollText, Search, Settings, Shield, Sun} from 'lucide-react';
|
||||
import {Bell, ChartColumn, Coins, Dices, Eye, Languages, LayoutDashboard, Library, Link2, LogOut, Mail, Megaphone, Moon, Newspaper, ScrollText, Search, Settings, Shield, Sun} from 'lucide-react';
|
||||
import {toast} from 'sonner';
|
||||
|
||||
import {Badge} from '@/components/ui/badge';
|
||||
@@ -25,6 +25,7 @@ import useCurrentUser from '../hooks/useCurrentUser.js';
|
||||
import useSteamProfile from '../hooks/useSteamProfile.js';
|
||||
import {fetchPendingCount, ROLES, viewAs} from '../utils/admin.js';
|
||||
import {canCorrect} from '../utils/corrections.js';
|
||||
import {fetchInbox} from '../utils/inbox.js';
|
||||
import {expireSession, markSession} from '../utils/auth.js';
|
||||
|
||||
const NAV_ITEMS = [
|
||||
@@ -70,6 +71,20 @@ export default function AppHeader({isAuthenticated, onLogout}) {
|
||||
}, [seesAdminPage, pathname, menuOpenings]);
|
||||
const shownCount = seesAdminPage ? pendingCount : 0;
|
||||
|
||||
// Loaded again on every page change. The inbox page marks the notes read, so it shows no count.
|
||||
const [unread, setUnread] = useState(0);
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) return undefined;
|
||||
let current = true;
|
||||
fetchInbox().then(({ok, data}) => {
|
||||
if (current && ok) setUnread(data.unread);
|
||||
});
|
||||
return () => {
|
||||
current = false;
|
||||
};
|
||||
}, [isAuthenticated, pathname]);
|
||||
const shownUnread = pathname === '/inbox' ? 0 : unread;
|
||||
|
||||
const switchRole = async (role) => {
|
||||
try {
|
||||
const response = await viewAs(role === currentUser.role ? null : role);
|
||||
@@ -133,6 +148,19 @@ export default function AppHeader({isAuthenticated, onLogout}) {
|
||||
<Link to="/points"><Coins className="text-primary"/><span className="min-w-0 truncate tabular-nums">{wallet.balance}</span></Link>
|
||||
</Button>
|
||||
)}
|
||||
{isAuthenticated && (
|
||||
<Button asChild variant="ghost" size="icon" title={t('NAV.INBOX')} className="relative shrink-0">
|
||||
<Link to="/inbox" aria-label={shownUnread > 0 ? t('NAV.INBOX_UNREAD', {count: shownUnread}) : t('NAV.INBOX')}>
|
||||
<Bell/>
|
||||
{shownUnread > 0 && (
|
||||
<span aria-hidden="true"
|
||||
className="absolute -top-1 -right-1 flex size-4 items-center justify-center rounded-full bg-primary text-[10px] font-bold text-primary-foreground">
|
||||
{shownUnread > 9 ? '9+' : shownUnread}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
{isAuthenticated ? (
|
||||
<DropdownMenu onOpenChange={(open) => open && setMenuOpenings((count) => count + 1)}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -154,6 +182,9 @@ export default function AppHeader({isAuthenticated, onLogout}) {
|
||||
<DropdownMenuItem asChild>
|
||||
<Link to="/points"><Coins/>{t('NAV.POINTS')}</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link to="/inbox"><Bell/>{t('NAV.INBOX')}</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link to="/whats-new"><Newspaper/>{t('NAV.WHATS_NEW')}</Link>
|
||||
</DropdownMenuItem>
|
||||
|
||||
Reference in new issue
Block a user