Index: app/(app)/history/account-filter.tsx
===================================================================
--- app/(app)/history/account-filter.tsx	(revision bdea5af318014340921ba4b5b90d91a9dd858321)
+++ app/(app)/history/account-filter.tsx	(revision 3e7a6b7451c51e34994dbbed21b43a46a400f8e2)
@@ -1,7 +1,3 @@
-'use client';
-
-import { useState, useRef, useEffect } from 'react';
-import { useSearchParams, usePathname, useRouter } from 'next/navigation';
-import { WalletIcon, CheckIcon } from '@heroicons/react/24/outline';
+import AccountFilterIcon from '@/app/ui/account-filter-icon';
 import type { TransactionAccountLite } from '@/app/lib/queries';
 
@@ -11,102 +7,4 @@
     accounts: TransactionAccountLite[];
 }) {
-    const searchParams = useSearchParams();
-    const pathname = usePathname();
-    const { replace } = useRouter();
-    const [open, setOpen] = useState(false);
-    const ref = useRef<HTMLDivElement>(null);
-
-    const currentId = searchParams.get('accountId') ?? '';
-    const hasFilter = currentId !== '';
-
-    // Close when clicking outside
-    useEffect(() => {
-        function onClickOutside(e: MouseEvent) {
-            if (ref.current && !ref.current.contains(e.target as Node)) {
-                setOpen(false);
-            }
-        }
-        if (open) {
-            document.addEventListener('mousedown', onClickOutside);
-            return () => document.removeEventListener('mousedown', onClickOutside);
-        }
-    }, [open]);
-
-    function select(value: string) {
-        const params = new URLSearchParams(searchParams);
-        params.set('page', '1');
-        if (value) {
-            params.set('accountId', value);
-        } else {
-            params.delete('accountId');
-        }
-        replace(`${pathname}?${params.toString()}`);
-        setOpen(false);
-    }
-
-    return (
-        <div ref={ref} className="relative">
-            {/* Icon trigger */}
-            <button
-                type="button"
-                onClick={() => setOpen((v) => !v)}
-                className={`
-                    flex items-center justify-center h-12 w-12 rounded-xl border transition
-                    ${hasFilter
-                        ? 'bg-blue-600 border-blue-500 text-white'
-                        : 'bg-white/15 border-white/15 text-white/60 hover:text-white hover:bg-white/20'
-                    }
-                `}
-                title="Filter by account"
-            >
-                <WalletIcon className="h-5 w-5" />
-            </button>
-
-            {/* Dropdown */}
-            {open && (
-                <div className="absolute right-0 top-14 z-50 w-56 rounded-xl bg-gray-900/95 border border-white/15 backdrop-blur-lg shadow-2xl py-1 overflow-hidden">
-                    <DropdownItem
-                        label="All Accounts"
-                        isSelected={currentId === ''}
-                        onClick={() => select('')}
-                    />
-                    {accounts.map((acc) => {
-                        const id = String(acc.transaction_account_id);
-                        return (
-                            <DropdownItem
-                                key={id}
-                                label={acc.account_name ?? `Account #${acc.transaction_account_id}`}
-                                isSelected={currentId === id}
-                                onClick={() => select(id)}
-                            />
-                        );
-                    })}
-                </div>
-            )}
-        </div>
-    );
+    return <AccountFilterIcon accounts={accounts} resetPageOnChange />;
 }
-
-function DropdownItem({
-    label,
-    isSelected,
-    onClick,
-}: {
-    label: string;
-    isSelected: boolean;
-    onClick: () => void;
-}) {
-    return (
-        <button
-            type="button"
-            onClick={onClick}
-            className={`
-                w-full flex items-center gap-2 px-4 py-2.5 text-sm text-left transition
-                ${isSelected ? 'text-white bg-white/10' : 'text-white/70 hover:bg-white/5 hover:text-white'}
-            `}
-        >
-            {isSelected && <CheckIcon className="h-4 w-4 shrink-0 text-blue-400" />}
-            <span className={isSelected ? '' : 'pl-6'}>{label}</span>
-        </button>
-    );
-}
Index: app/(app)/history/search.tsx
===================================================================
--- app/(app)/history/search.tsx	(revision bdea5af318014340921ba4b5b90d91a9dd858321)
+++ app/(app)/history/search.tsx	(revision 3e7a6b7451c51e34994dbbed21b43a46a400f8e2)
@@ -1,35 +1,5 @@
-'use client';
-
-import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
-import { useSearchParams, usePathname, useRouter } from 'next/navigation';
-import { useDebouncedCallback } from 'use-debounce';
-import { poppins } from '@/app/ui/fonts';
+import UrlSearchInput from '@/app/ui/url-search-input';
 
 export default function Search({ placeholder }: { placeholder: string }) {
-    const searchParams = useSearchParams();
-    const pathname = usePathname();
-    const { replace } = useRouter();
-
-    const handleSearch = useDebouncedCallback((term: string) => {
-        const params = new URLSearchParams(searchParams);
-        params.set('page', '1');
-        if (term) {
-            params.set('query', term);
-        } else {
-            params.delete('query');
-        }
-        replace(`${pathname}?${params.toString()}`);
-    }, 300);
-
-    return (
-        <div className="relative">
-            <MagnifyingGlassIcon className="pointer-events-none absolute left-3.5 top-1/2 h-5 w-5 -translate-y-1/2 text-white/60" />
-            <input
-                className={`${poppins.className} w-full h-12 rounded-xl bg-white/15 border border-white/15 pl-11 pr-4 text-white placeholder:text-white/50 focus:outline-none focus:ring-2 focus:ring-blue-500/60 focus:border-blue-500/40 transition`}
-                placeholder={placeholder}
-                onChange={(e) => handleSearch(e.target.value)}
-                defaultValue={searchParams.get('query')?.toString()}
-            />
-        </div>
-    );
+    return <UrlSearchInput placeholder={placeholder} resetPageOnChange />;
 }
Index: app/ui/account-filter-icon.tsx
===================================================================
--- app/ui/account-filter-icon.tsx	(revision 3e7a6b7451c51e34994dbbed21b43a46a400f8e2)
+++ app/ui/account-filter-icon.tsx	(revision 3e7a6b7451c51e34994dbbed21b43a46a400f8e2)
@@ -0,0 +1,119 @@
+'use client';
+
+import { useState, useRef, useEffect } from 'react';
+import { useSearchParams, usePathname, useRouter } from 'next/navigation';
+import { WalletIcon, CheckIcon } from '@heroicons/react/24/outline';
+import type { TransactionAccountLite } from '@/app/lib/queries';
+
+export default function AccountFilterIcon({
+    accounts,
+    accountParam = 'accountId',
+    resetPageOnChange = false,
+}: {
+    accounts: TransactionAccountLite[];
+    accountParam?: string;
+    resetPageOnChange?: boolean;
+}) {
+    const searchParams = useSearchParams();
+    const pathname = usePathname();
+    const { replace } = useRouter();
+    const [open, setOpen] = useState(false);
+    const ref = useRef<HTMLDivElement>(null);
+
+    const currentId = searchParams.get(accountParam) ?? '';
+    const hasFilter = currentId !== '';
+
+    useEffect(() => {
+        function onClickOutside(e: MouseEvent) {
+            if (ref.current && !ref.current.contains(e.target as Node)) {
+                setOpen(false);
+            }
+        }
+        if (open) {
+            document.addEventListener('mousedown', onClickOutside);
+            return () => document.removeEventListener('mousedown', onClickOutside);
+        }
+    }, [open]);
+
+    function select(value: string) {
+        const params = new URLSearchParams(searchParams);
+
+        if (resetPageOnChange) {
+            params.set('page', '1');
+        }
+
+        if (value) {
+            params.set(accountParam, value);
+        } else {
+            params.delete(accountParam);
+        }
+
+        const nextQuery = params.toString();
+        replace(nextQuery ? `${pathname}?${nextQuery}` : pathname);
+        setOpen(false);
+    }
+
+    return (
+        <div ref={ref} className="relative">
+            <button
+                type="button"
+                onClick={() => setOpen((v) => !v)}
+                className={`
+                    flex items-center justify-center h-12 w-12 rounded-xl border transition
+                    ${hasFilter
+                        ? 'bg-blue-600 border-blue-500 text-white'
+                        : 'bg-white/15 border-white/15 text-white/60 hover:text-white hover:bg-white/20'
+                    }
+                `}
+                title="Filter by account"
+            >
+                <WalletIcon className="h-5 w-5" />
+            </button>
+
+            {open && (
+                <div className="absolute right-0 top-14 z-50 w-56 rounded-xl bg-gray-900/95 border border-white/15 backdrop-blur-lg shadow-2xl py-1 overflow-hidden">
+                    <DropdownItem
+                        label="All Accounts"
+                        isSelected={currentId === ''}
+                        onClick={() => select('')}
+                    />
+                    {accounts.map((acc) => {
+                        const id = String(acc.transaction_account_id);
+                        return (
+                            <DropdownItem
+                                key={id}
+                                label={acc.account_name ?? `Account #${acc.transaction_account_id}`}
+                                isSelected={currentId === id}
+                                onClick={() => select(id)}
+                            />
+                        );
+                    })}
+                </div>
+            )}
+        </div>
+    );
+}
+
+function DropdownItem({
+    label,
+    isSelected,
+    onClick,
+}: {
+    label: string;
+    isSelected: boolean;
+    onClick: () => void;
+}) {
+    return (
+        <button
+            type="button"
+            onClick={onClick}
+            className={`
+                w-full flex items-center gap-2 px-4 py-2.5 text-sm text-left transition
+                ${isSelected ? 'text-white bg-white/10' : 'text-white/70 hover:bg-white/5 hover:text-white'}
+            `}
+        >
+            {isSelected && <CheckIcon className="h-4 w-4 shrink-0 text-blue-400" />}
+            <span className={isSelected ? '' : 'pl-6'}>{label}</span>
+        </button>
+    );
+}
Index: app/ui/url-search-input.tsx
===================================================================
--- app/ui/url-search-input.tsx	(revision 3e7a6b7451c51e34994dbbed21b43a46a400f8e2)
+++ app/ui/url-search-input.tsx	(revision 3e7a6b7451c51e34994dbbed21b43a46a400f8e2)
@@ -0,0 +1,51 @@
+'use client';
+
+import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
+import { useSearchParams, usePathname, useRouter } from 'next/navigation';
+import { useDebouncedCallback } from 'use-debounce';
+import { poppins } from '@/app/ui/fonts';
+
+export default function UrlSearchInput({
+    placeholder,
+    queryParam = 'query',
+    debounceMs = 300,
+    resetPageOnChange = false,
+}: {
+    placeholder: string;
+    queryParam?: string;
+    debounceMs?: number;
+    resetPageOnChange?: boolean;
+}) {
+    const searchParams = useSearchParams();
+    const pathname = usePathname();
+    const { replace } = useRouter();
+
+    const handleSearch = useDebouncedCallback((term: string) => {
+        const params = new URLSearchParams(searchParams);
+
+        if (resetPageOnChange) {
+            params.set('page', '1');
+        }
+
+        if (term) {
+            params.set(queryParam, term);
+        } else {
+            params.delete(queryParam);
+        }
+
+        const nextQuery = params.toString();
+        replace(nextQuery ? `${pathname}?${nextQuery}` : pathname);
+    }, debounceMs);
+
+    return (
+        <div className="relative">
+            <MagnifyingGlassIcon className="pointer-events-none absolute left-3.5 top-1/2 h-5 w-5 -translate-y-1/2 text-white/60" />
+            <input
+                className={`${poppins.className} w-full h-12 rounded-xl bg-white/15 border border-white/15 pl-11 pr-4 text-white placeholder:text-white/50 focus:outline-none focus:ring-2 focus:ring-blue-500/60 focus:border-blue-500/40 transition`}
+                placeholder={placeholder}
+                onChange={(e) => handleSearch(e.target.value)}
+                defaultValue={searchParams.get(queryParam)?.toString()}
+            />
+        </div>
+    );
+}
