Is your feature request related to a problem? Please describe.
List blocks (ticket-list, order-list, invoice-list, product-list, notification-list) manage filter state locally via useState + Formik. When a user applies filters, the URL does not update — meaning filtered views cannot be shared, bookmarked, or linked to from other parts of the application (e.g. clickable status boxes navigating to a pre-filtered list).
Describe the solution you'd like
Implement bidirectional synchronization between filter state and URL query parameters across all list blocks.
Key requirements:
- New
useUrlFilters hook in packages/ui/src/hooks/ that replaces useState(initialFilters) in list blocks
- Serialization utilities for converting filter state to/from URL query params
- Namespace-per-block prefixing (
{ns}_key=value) to avoid collisions when multiple blocks appear on the same page
- Repeated keys for multi-select values (e.g.
status=open&status=pending)
- Pagination as
{ns}_page=N (1-based, human-readable)
- View mode in URL (
{ns}_view=grid), omitted when default (list)
- Only non-default filter values written to URL (clean URLs)
router.replace for filter changes (no browser history pollution)
- Dependency injection pattern — hook accepts
searchParams and onUrlChange as arguments (no direct next/navigation imports), making it framework-agnostic
Blocks to migrate:
ticket-list (proof of concept)
order-list
invoice-list
product-list
notification-list
Benefits:
- Shareable/bookmarkable filtered views
- Clickable UI elements (e.g. status summary boxes) can navigate to pre-filtered lists
- Backward compatible — blocks without the hook continue working as before
Additional context (if applicable)
Implementation approach
New hook: useUrlFilters (packages/ui/src/hooks/use-url-filters.ts)
Replaces useState(initialFilters) in list blocks. Uses dependency injection — accepts searchParams and onUrlChange as arguments instead of importing from next/navigation directly:
interface UseUrlFiltersOptions<TFilters extends Record<string, string | number | string[]>> {
initialFilters: TFilters;
namespace?: string; // prefix for URL params, e.g. 'ticket'
excludeKeys?: (keyof TFilters)[];
searchParams: URLSearchParams;
onUrlChange: (params: string) => void;
}
Serialization rules:
- Simple values:
ticket_status=open
- Multi-value:
ticket_status=open&ticket_status=pending (repeated keys)
- Pagination:
ticket_page=2 (1-based, derived from offset/limit)
- View mode:
ticket_view=grid (omitted when default list)
- Only non-default values written to URL
New utility functions (packages/ui/src/hooks/use-url-filters.utils.ts):
serializeFiltersToParams() — filter state → URL query string
deserializeParamsToFilters() — URL query string → filter state
filtersToPage() / pageToOffset() — pagination helpers
Files to create
packages/ui/src/hooks/use-url-filters.ts — the hook
packages/ui/src/hooks/use-url-filters.utils.ts — serialization/deserialization utilities
packages/ui/src/hooks/use-url-filters.test.ts — unit tests
Files to modify
packages/blocks/ticket-list/src/frontend/TicketList.client.tsx
packages/blocks/order-list/src/frontend/OrderList.client.tsx
packages/blocks/invoice-list/src/frontend/InvoiceList.client.tsx
packages/blocks/product-list/src/frontend/ProductList.client.tsx
packages/blocks/notification-list/src/frontend/NotificationList.client.tsx
Verification checklist
This repo is using Opire - what does it mean? 👇
💵 Everyone can add rewards for this issue commenting /reward 100 (replace 100 with the amount).
🕵️♂️ If someone starts working on this issue to earn the rewards, they can comment /try to let everyone know!
🙌 And when they open the PR, they can comment /claim #764 either in the PR description or in a PR's comment.
🪙 Also, everyone can tip any user commenting /tip 20 @michnowak (replace 20 with the amount, and @michnowak with the user to tip).
📖 If you want to learn more, check out our documentation.
Is your feature request related to a problem? Please describe.
List blocks (ticket-list, order-list, invoice-list, product-list, notification-list) manage filter state locally via
useState+ Formik. When a user applies filters, the URL does not update — meaning filtered views cannot be shared, bookmarked, or linked to from other parts of the application (e.g. clickable status boxes navigating to a pre-filtered list).Describe the solution you'd like
Implement bidirectional synchronization between filter state and URL query parameters across all list blocks.
Key requirements:
useUrlFiltershook inpackages/ui/src/hooks/that replacesuseState(initialFilters)in list blocks{ns}_key=value) to avoid collisions when multiple blocks appear on the same pagestatus=open&status=pending){ns}_page=N(1-based, human-readable){ns}_view=grid), omitted when default (list)router.replacefor filter changes (no browser history pollution)searchParamsandonUrlChangeas arguments (no directnext/navigationimports), making it framework-agnosticBlocks to migrate:
ticket-list(proof of concept)order-listinvoice-listproduct-listnotification-listBenefits:
Additional context (if applicable)
Implementation approach
New hook:
useUrlFilters(packages/ui/src/hooks/use-url-filters.ts)Replaces
useState(initialFilters)in list blocks. Uses dependency injection — acceptssearchParamsandonUrlChangeas arguments instead of importing fromnext/navigationdirectly:Serialization rules:
ticket_status=openticket_status=open&ticket_status=pending(repeated keys)ticket_page=2(1-based, derived from offset/limit)ticket_view=grid(omitted when defaultlist)New utility functions (
packages/ui/src/hooks/use-url-filters.utils.ts):serializeFiltersToParams()— filter state → URL query stringdeserializeParamsToFilters()— URL query string → filter statefiltersToPage()/pageToOffset()— pagination helpersFiles to create
packages/ui/src/hooks/use-url-filters.ts— the hookpackages/ui/src/hooks/use-url-filters.utils.ts— serialization/deserialization utilitiespackages/ui/src/hooks/use-url-filters.test.ts— unit testsFiles to modify
packages/blocks/ticket-list/src/frontend/TicketList.client.tsxpackages/blocks/order-list/src/frontend/OrderList.client.tsxpackages/blocks/invoice-list/src/frontend/InvoiceList.client.tsxpackages/blocks/product-list/src/frontend/ProductList.client.tsxpackages/blocks/notification-list/src/frontend/NotificationList.client.tsxVerification checklist
?ticket_page=2)?ticket_view=grid)This repo is using Opire - what does it mean? 👇
💵 Everyone can add rewards for this issue commenting
/reward 100(replace100with the amount).🕵️♂️ If someone starts working on this issue to earn the rewards, they can comment
/tryto let everyone know!🙌 And when they open the PR, they can comment
/claim #764either in the PR description or in a PR's comment.🪙 Also, everyone can tip any user commenting
/tip 20 @michnowak(replace20with the amount, and@michnowakwith the user to tip).📖 If you want to learn more, check out our documentation.