Dashboard Filters
| Page | app/(dashboard)/dashboard/page.tsx |
| Route | /dashboard |
| Access | "Filters" button in the Your Library section |
| Scope | Filters operate entirely client-side on the user's loaded stories |
What it does
The dashboard library includes a collapsible filter panel alongside the existing search bar. Users can narrow their story list by sort order, visibility (public/private), and language — without any additional network requests, since all stories are already loaded from Firestore.
Filter options
Search
A real-time text input that matches story titles as the user types. Pressing the × inline clears the field.
| Detail | Value |
|---|---|
| Field | title (case-insensitive includes) |
| Trigger | Instant — runs on every keystroke |
| Clear | Inline × button or "Clear Filters" |
Sort
Controls the display order of the filtered story list.
| Option | Behaviour |
|---|---|
| Newest First | Default — stories ordered by createdAt descending (Firestore default) |
| Oldest First | Reverses the Firestore-ordered array |
| A → Z | Alphabetical sort on title using localeCompare |
Visibility
Filters by whether the story has been shared publicly.
| Option | Behaviour |
|---|---|
| All | Default — shows every story |
| Public | Shows only stories where isPublic === true |
| Private | Shows only stories where isPublic === false (or unset) |
Language
Filters stories by the language they were generated in. This filter only appears when the user has stories in more than one language — if all stories share the same language, the section is hidden.
Language codes are derived automatically from the user's story library at runtime (availableLanguages is computed with useMemo).
| Value | Example label |
|---|---|
en | en |
tet | tet |
id | id |
| (any) | shown as-is |
UI behaviour
- Filter toggle button — sits next to the search bar; labelled "Filters" on desktop, icon-only on mobile.
- Active filter badge — a count bubble on the Filters button shows how many of the three filters (sort, visibility, language) are non-default.
- "Clear" pill — appears in the section title row whenever any filter is active; resets all filters and search in one click.
- Animated panel — the filter panel slides open/closed with a
height+opacityanimation (framer-motion). - Page reset — changing any filter resets pagination back to page 1.
- Empty state — when filters return zero results, the message changes to "Try adjusting your filters or search" and the action button becomes "Clear Filters" instead of "Create First Story".
- Theme-aware — all filter UI respects the user's dark/light theme preference stored in
localStorage.
Data flow
useUserStories(uid)
│ (Firestore real-time listener, ordered by createdAt desc)
▼
stories[] ←──────────────────────────────────────────┐
│ │
▼ │
filteredStories = useMemo(() => { │
filter by searchQuery (title) │
filter by filterLanguage (language) │
filter by filterVisibility (isPublic) │
sort by filterSort (newest / oldest / az) │
}) │
│ │
▼ re-runs on
paginatedStories = slice(startIndex, endIndex) story update
│
▼
Rendered as <StoryCard> gridAll filtering and sorting happens inside a single useMemo — no additional Firestore reads are triggered.
State variables
| Variable | Type | Default | Purpose |
|---|---|---|---|
searchQuery | string | "" | Title search input value |
filterSort | "newest" | "oldest" | "az" | "newest" | Active sort order |
filterVisibility | "" | "public" | "private" | "" | Visibility filter |
filterLanguage | string | "" | Language code filter |
showFilters | boolean | false | Whether the panel is expanded |
currentPage | number | 1 | Resets to 1 on any filter change |
Design decisions
- Client-side only — stories are already loaded by the Firestore real-time listener. Sending filter params back to the server would require an extra round-trip and lose real-time updates.
- Language filter hidden when not needed — showing a language selector with only one option adds noise. It only renders when
availableLanguages.length > 1. - Single
clearFiltersfunction — resets all four filter states at once, keeping clear-all behaviour consistent regardless of which combination of filters is active. useMemofor filtering — avoids re-running the filter/sort logic on every render; only recalculates whenstories,searchQuery,filterLanguage,filterVisibility, orfilterSortchange.
