Skip to content

Dashboard Filters

Pageapp/(dashboard)/dashboard/page.tsx
Route/dashboard
Access"Filters" button in the Your Library section
ScopeFilters 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

A real-time text input that matches story titles as the user types. Pressing the × inline clears the field.

DetailValue
Fieldtitle (case-insensitive includes)
TriggerInstant — runs on every keystroke
ClearInline × button or "Clear Filters"

Sort

Controls the display order of the filtered story list.

OptionBehaviour
Newest FirstDefault — stories ordered by createdAt descending (Firestore default)
Oldest FirstReverses the Firestore-ordered array
A → ZAlphabetical sort on title using localeCompare

Visibility

Filters by whether the story has been shared publicly.

OptionBehaviour
AllDefault — shows every story
PublicShows only stories where isPublic === true
PrivateShows 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).

ValueExample label
enen
tettet
idid
(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 + opacity animation (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> grid

All filtering and sorting happens inside a single useMemo — no additional Firestore reads are triggered.


State variables

VariableTypeDefaultPurpose
searchQuerystring""Title search input value
filterSort"newest" | "oldest" | "az""newest"Active sort order
filterVisibility"" | "public" | "private"""Visibility filter
filterLanguagestring""Language code filter
showFiltersbooleanfalseWhether the panel is expanded
currentPagenumber1Resets 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 clearFilters function — resets all four filter states at once, keeping clear-all behaviour consistent regardless of which combination of filters is active.
  • useMemo for filtering — avoids re-running the filter/sort logic on every render; only recalculates when stories, searchQuery, filterLanguage, filterVisibility, or filterSort change.

Released under the MIT License.