8000 feat(huddlz): personal sections + URL-driven filters on /huddlz by mwoods79 · Pull Request #163 · huddlz-hq/huddlz · GitHub
[go: up one dir, main page]

Skip to content

feat(huddlz): personal sections + URL-driven filters on /huddlz - #163

Merged
mwoods79 merged 8 commits into
mainfrom
feat/huddlz-personal-sections
May 2, 2026
Merged

mwoods79 merged 8 commits into
mainfrom
feat/huddlz-personal-sections

Conversation

@mwoods79
@mwoods79 mwoods79 commented May 2, 2026
Copy link
Copy Markdown
Contributor

Summary

Mirrors the /groups personal-sections treatment on the huddlz discovery page (#157 was the precedent), and migrates the page's filter state into the URL so deep links and back/forward navigation work properly.

What's new

  • `// HOSTING (n)` section — huddlz where the actor is the creator
  • `// ATTENDING (n)` section — huddlz the actor RSVPed to but did not create (so a host who RSVPed to their own huddl isn't double-counted)
  • Sections inherit every filter: search, event type, date range, location/distance — they scope alongside the main grid, not orthogonally
  • Each section caps at 6 cards with a `View all →` link to `/?yours=hosting` or `/?yours=attending` that preserves all active filters
  • `?yours=hosting` and `?yours=attending` render a full scoped grid with an `← All huddlz` back link
  • Anonymous users hitting a scoped URL are redirected to `/sign-in` with a flash; the unscoped `/` is unchanged for anonymous

URL-driven filter state

The page previously kept search/type/date/location in socket assigns only. Now it's URL-driven (via `handle_params` + `push_patch`), so:

  • Deep links like `/?q=elixir&date_filter=this_week&yours=attending` reproduce state
  • Back/forward navigation works as expected
  • Sharing a URL preserves the filters

Resource & API surface

Per the API-first principle:

  • New optional `:relationship` argument on the existing `Huddl.search` action (`:hosting | :attending`)
  • Anonymous actor + relationship filter returns `[]` (rather than silently broadening scope)
  • Exposed in GraphQL as `searchHuddlz(relationship: "hosting" | "attending")`
  • The new filter composes with all existing search filters in a single query — no fan-out to additional actions

Trade-offs worth flagging

  • Same-card-twice trade-off as /groups: a huddl you host appears in `// HOSTING` and (if it matches the broader filter) in the main grid below. Standard pattern; intentional.
  • Three queries when authenticated: hosting + attending + main grid. Single trigram + actor-scoped query each; cheap. Revisit if metrics show pressure.
  • Section sort is `starts_at` ascending — soonest first, which matches the existing main-grid sort.

Out of scope

  • The `Huddl.search` action's existing free-form `:query` arg uses `contains/2`; converting it to trigram + relevance ranking (matching /groups) is a separate change.
  • A unified `prepare` helper that both /groups and /huddlz could share — defer until we have a third caller.

Test plan

  • `/` as anonymous — no personal sections
  • `/` logged in with no relationships — no personal sections, page unchanged
  • `/` logged in as host — `// Hosting` section appears
  • `/` logged in as RSVPed user — `// Attending` section appears
  • Host who also RSVPed their own huddl — appears in Hosting only, not Attending
  • Type a search query — sections and main grid filter together; non-matching sections disappear
  • Apply event type or date filter — same: sections and main grid scope together
  • Pick a location with distance — sections and main grid both filter by location
  • `?yours=hosting` / `?yours=attending` — single grid, scoped page title, back link
  • Click `View all →` with active filters — destination URL preserves `q`, `event_type`, `date_filter`, `location`/`lat`/`lng`/`distance_miles`
  • Anonymous → `/?yours=hosting` → redirected to `/sign-in` with flash
  • Browser back/forward — filter state restored from URL

mwoods79 added 3 commits May 2, 2026 01:12
Adds an optional :relationship argument to Huddl :search action that
restricts results to huddlz the actor hosts (creator) or is attending
(RSVPed but not creator). Anonymous actors with the filter set get an
empty result rather than a silent broadening of scope.

The filter is applied in the existing ApplySearchFilters preparation
so it composes with all other search filters (text, date, event type,
location/distance) in a single query.
Mirrors the /groups treatment on the huddlz discovery page.

- Two personal sections at the top: "// Hosting" and "// Attending"
  that appear when the authenticated actor has matching huddlz
- Each section caps at 6 cards and shows "View all →" when count
  exceeds the cap, deep-linking to /?yours=hosting|attending while
  preserving every active filter (search, type, date, location)
- Sections inherit ALL filters: search, event type, date filter, and
  location/distance scope sections together with the main grid
- ?yours= scoped views render a single full grid scoped to that
  relationship with a back link
- Filters are now URL-driven so deep links reproduce state and back
  button works as expected
- Anonymous users hitting ?yours= are redirected to /sign-in with a
  flash explaining what they were after

Tests: LiveView (sections, scope routing, anon redirect, search-aware
empty copy, View-all href), GraphQL (relationship arg on searchHuddlz),
cucumber feature for the headline flow.
- View-all link now threads cleared=1 when the user has a default
  location pre-fill that they explicitly cleared, so navigating into
  a scoped section doesn't resurrect the home_location filter.
- Drop distance from the URL when the location is cleared. Previously,
  clicking the Clear button while a default location was active could
  emit URLs like /?distance=25&cleared=1 because form_params_from_assigns
  read stale assigns; distance is meaningless without a location.
- Refactor current_filter_params into smaller helpers (location_params,
  non_location_params, drop_param?) to drop credo complexity warning.
mwoods79 added 5 commits May 2, 2026 09:39
The location autocomplete only renders a hidden `location` text input;
lat/lng live in component state and reach the parent only via the
:location_selected message. Plain form events (typing search, changing
event type or date) therefore submitted without lat/lng, and
current_filter_params dropped the location filter from the URL — the
location chip and distance filter silently disappeared.

Merge the active socket location into form params before serializing
the path, so URL state stays in sync when unrelated filters change.
The link out of a scoped view (?yours=hosting|attending) was
hardcoded to ~p"/" and dropped every active filter, even though the
inbound "View all →" link intentionally preserves them. Build the
back-link URL from current filter assigns with scope=:all so the
round trip is symmetric.
The :relationship arg lived on the action but not on the
Communities.search_huddlz code interface, so external callers (and
the LiveView) had to bypass the helper and build an Ash query by
hand. Add it as the trailing optional positional and switch the
LiveView's run_search/3 back to the helper for consistency with
the rest of the codebase.

Existing 6-arg call sites need an explicit nil before opts so the
keyword list isn't picked up as the 7th positional.
CLAUDE.md mandates lowercase "huddlz" in all branding, UI copy, and
documentation. The flash messages already followed this; the scoped
page titles ("Huddlz You're Hosting", "Huddlz You're Attending",
"Huddlz") didn't.
The previous comment claimed the autocomplete fires :location_cleared
on its own initial mount with a nil value; it doesn't — the only emit
site is the "clear" event handler. The guard's real purpose is
idempotence: avoid polluting the URL with cleared=1 when there was
nothing to clear. Update the comment to match.
@mwoods79
mwoods79 merged commit 7830c6a into main May 2, 2026
1 check passed
@mwoods79
mwoods79 deleted the feat/huddlz-personal-sections branch May 2, 2026 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0