feat: saved searches

Lets users name and pin a search query from the SearchResults page;
saved searches persist to localStorage and show as quick links in a new
sidebar section, similar to Favorites. Clicking re-runs the query.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-06-30 22:00:12 +02:00
parent c7ead8caae
commit 9bd3799fbc
3 changed files with 79 additions and 3 deletions
+34 -2
View File
@@ -4,6 +4,7 @@ import { AuthApi, SyncApi, AnalyticsApi } from '../api/client.js';
import Logo from './Logo.jsx';
import DevBanner from './DevBanner.jsx';
import SyncStatus from './SyncStatus.jsx';
import useSavedSearches from '../hooks/useSavedSearches.js';
// ── Folder definitions ────────────────────────────────────────────────────────
@@ -78,8 +79,8 @@ const loadFavs = () => {
const saveFavs = (v) => localStorage.setItem(LS_FAV, JSON.stringify(v));
const loadSections = () => {
try { return JSON.parse(localStorage.getItem(LS_SECTS)) ?? { fav: true, mailbox: true, smart: true }; }
catch { return { fav: true, mailbox: true, smart: true }; }
try { return JSON.parse(localStorage.getItem(LS_SECTS)) ?? { fav: true, mailbox: true, smart: true, saved: true }; }
catch { return { fav: true, mailbox: true, smart: true, saved: true }; }
};
const saveSections = (v) => localStorage.setItem(LS_SECTS, JSON.stringify(v));
@@ -126,6 +127,7 @@ export default function Layout() {
const [sections, setSections] = useState(loadSections);
const [favSlugs, setFavSlugs] = useState(loadFavs);
const [counts, setCounts] = useState(null);
const { searches: savedSearches, remove: removeSavedSearch } = useSavedSearches();
const loc = useLocation();
const navigate = useNavigate();
@@ -291,6 +293,36 @@ export default function Layout() {
</ul>
)}
{/* ── Saved Searches ── */}
{savedSearches.length > 0 && (
<>
<SectionHead label="Saved Searches" open={sections.saved} onToggle={() => toggleSection('saved')} collapsed={!sidebarOpen} />
{sections.saved && (
<ul className="folder-list">
{savedSearches.map((s) => (
<li key={s.id} className="saved-search-row">
<Link
to={`/app/search?q=${encodeURIComponent(s.query)}`}
className={`folder-item${loc.pathname === '/app/search' && searchParams.get('q') === s.query ? ' folder-item--active' : ''}`}
title={s.query}
>
<span className="folder-icon">🔎</span>
{sidebarOpen && <span className="folder-label">{s.label}</span>}
</Link>
{sidebarOpen && (
<button
className="fav-pin fav-pin--remove"
title="Remove saved search"
onClick={(e) => { e.preventDefault(); removeSavedSearch(s.id); }}
></button>
)}
</li>
))}
</ul>
)}
</>
)}
{/* ── Mailbox ── */}
<SectionHead label="Mailbox" open={sections.mailbox} onToggle={() => toggleSection('mailbox')} collapsed={!sidebarOpen} />
{sections.mailbox && (