Skip to content

Austin Ban - Frontend: Met Collection search app (React + Vite) - #331

Open
austinban wants to merge 6 commits into
Tekmetric:masterfrom
austinban:met-collection-search
Open

Austin Ban - Frontend: Met Collection search app (React + Vite)#331
austinban wants to merge 6 commits into
Tekmetric:masterfrom
austinban:met-collection-search

Conversation

@austinban

@austinban austinban commented Jul 15, 2026

Copy link
Copy Markdown

Note from the author

I'm sure I went slightly beyond scope for this project, I just started having a really great time with it! I even had to strip back scope I built out such as summary pages, multiple language support, etc... Hoping to lean back into some simplicity to make it easier to review and respect you all's time. Looking forward to hearing back from you all and maybe can work together in the future.

Cheers,
Austin

Summary

A single-page app for the Frontend interview that searches The Metropolitan Museum of Art's open collection (keyless, CORS-enabled public API) and lets you browse, inspect, and save works. Built with React + Vite + Tailwind. All changes are under frontend/.

Highlights:

  • Search + browse. Debounced query and department filter, with the URL (?q, ?dept) as the single source of truth — so Back/Forward, shareable links, and a nav-link back to the empty state all just work. Results page in lazily (12 at a time) and skip individual objects that fail to load rather than failing the whole page.
  • Landing / empty state. Before any search, a single card explains the app and offers categorized public-domain example searches that kick off a real query.
  • Artwork detail + lightbox. Accessible dialog with a full-screen image view (click image or backdrop to dismiss); focus trap and aria-modal are released on the underlying dialog while the lightbox is stacked.
  • Saved collection. Persisted to localStorage; exportable.
  • Accessibility. Keyboard-navigable dialogs and focus management, aria-live result counts, skip link, prefers-reduced-motion, a dedicated form-control border token meeting WCAG 1.4.11 (~3:1), and sr-only public-domain labels where the visible mark is hidden on mobile.
  • Resilience. One retry/backoff policy in the API layer for rate-limited/5xx responses; light/dark theming with system detection.
  • Tests. Vitest coverage for the debounce hook, the paging race condition, and URL sync (including Back/Forward and clearing via a nav link).

Test plan

  • cd frontend && npm install
  • npm run dev — search (e.g. "Vermeer"), filter by department, open a work, zoom the image, save/unsave, and export the collection
  • Use Back/Forward and a hand-edited ?q= to confirm the field and results stay in sync
  • Toggle light/dark and verify keyboard-only navigation through dialogs
  • npm test (18 tests) and npm run build

Made with in Cursor using Opus 4.8

Note: the frontend was rebuilt from the original Create React App scaffold to Vite, which is why the lockfile changed from yarn.lock to package-lock.json.

austinban and others added 5 commits July 14, 2026 22:12
A client-side single-page app for searching The Metropolitan Museum of Art's
open Collection API. Replaces the old Create React App scaffold with a Vite +
React 18 setup.

- Two-step data flow: search returns object IDs; details are fetched and paged
  lazily (load-more), a page of objects in parallel.
- Resilient paging: per-object fetch failures are counted and surfaced to the
  user rather than silently dropped.
- Query + department filter mirrored to the URL (two-way), debounced, with
  stale requests aborted so a slow search can't overwrite a newer one.
- Saved "Collection" (localStorage) with CSV export; accessible detail modal.
- Light/dark theme, 5-language custom Intl-based i18n, WCAG AA foundations.
- Vitest unit tests for the pure helpers (normalize, medium translation, CSV).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
The focus trap filters candidates by offsetParent, which is null for
position: fixed elements — so the zoomed-image close button was
unreachable by keyboard. Anchor it to a positioned container instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
Trims the app back toward the take-home brief:

- Remove the i18n system (LocaleProvider, t(), five locale catalogs, the
  language switcher). Copy is now inlined English at each call site, with
  small helpers for the pluralized/count strings; result counts still use
  Intl.NumberFormat. Drops the medium translation table with it.
- Remove the "How I built this" essay page and its lazy route (and the
  now-unused Spinner used only for its Suspense fallback).
- Drop "WCAG 2.x Level AA" claims from README, PLAN, and code comments while
  keeping the actual accessibility work (skip link, focus trap, labeled
  controls, live regions, focus-visible rings, reduced-motion).

Co-authored-by: Cursor <cursoragent@cursor.com>
- Landing/empty state: no default search runs; show an intro plus example
  searches grouped by category (public-domain-skewed terms) that run a real
  search when picked. Restore the focus-dropdown quick picks, derived from the
  same category data.
- Search history: push distinct queries as history entries so Back/Forward walk
  the search history; sync the debounce on navigation so results update without
  lag; bypass the debounce for explicit suggestion picks.
- Rename the app to "Meet the Met".
- Lightbox: close on any click (image or outside) and add cursor affordances.
- Select: custom caret with proper right padding (native arrow was flush).
- Artwork row: move corner clipping to the thumbnail so tooltips aren't cut off.
- Reduce page size to 12 to ease API rate limiting.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Simplify search URL sync: track the last-written query so the field follows
  any external navigation (Back/Forward, nav link to the empty state, edited
  URL) without clobbering typing; drop the POP-only special case.
- Model page-load failure as a first-class view state, fixing an error panel
  that briefly flashed on the happy path; remove the redundant focus dropdown.
- Accessibility: dedicated form-control border token meeting WCAG 1.4.11 (3:1);
  release the underlying dialog's focus trap / aria-modal when the lightbox
  stacks; sr-only public-domain label on mobile rows; decorative tooltip.
- Resilience: one retry/backoff policy in the API layer so rate-limited object
  fetches aren't silently dropped; simplify useDepartments accordingly.
- Migrate localStorage keys to the new name without orphaning saved data.
- Softer error copy plus a themed, transparent, size-optimized illustration.
- Add hook tests (debounce, paging race, URL sync incl. Back and nav-clear).

Co-authored-by: Cursor <cursoragent@cursor.com>
@austinban austinban changed the title Frontend: Met Collection search app (React + Vite) Austin Ban - Frontend: Met Collection search app (React + Vite) Jul 15, 2026
Prefix the header logo and error-state illustration with import.meta.env.BASE_URL
so they resolve under a non-root base path (e.g. /interview/ on GitHub Pages).
Hardcoded absolute paths aren't rewritten by Vite the way index.html and CSS
url() refs are. No effect locally, where BASE_URL is "/".

Co-authored-by: Cursor <cursoragent@cursor.com>
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