Skip to content

feat: UX Improvements + SSG additions#43

Open
Alessandro100 wants to merge 17 commits intomainfrom
feat/23-feeds-ux
Open

feat: UX Improvements + SSG additions#43
Alessandro100 wants to merge 17 commits intomainfrom
feat/23-feeds-ux

Conversation

@Alessandro100
Copy link
Contributor

Closes #23 #40 and #42

Summary:

Feed Search Page

  • Removed Redux in favour of SWC (data fetching library by Vercel)
  • Feed search queries are cached client side for 30 minutes
  • Removed from legacy router to App Router
  • Improved loading UX for navigating to Feed Detail Page (to follow up on)
  • New minimal loading state for feeds

Feed Detail Page

  • Included "generated at" under the page header
  • Removed data fetch from feed detail layouts in favour of the direct pages

New Static Site Generated pages

  • Static routes were removed from legacy router in favour of App Router
  • They are built at build time
  • Updated Contact Us to point to new repo

General

  • Fixed redux bug of: login -> logout -> login
  • Fixed logo image on mobile

Expected behavior:

  1. The feed search page should work as normal with good loading feedback
  2. The urls should work as normal (fast)
faq 
contact-us 
gbfs-validator
contribute-faq
privacy-policy
terms-and-conditions
  1. You should be able to login-logout-login no issue

Testing tips:

Use the feed search and play around with it (for extra verification set network to 4g slow)
Visit the affected urls to assure they display and behave correctly
Login - logout - login (test the login system)

Follow up tickets (not MVP)

  • Deeper investigation why navigation from Feeds Search to Feed Detail Page take a decent amount of time
  • Look into best practices on setting the firebase anonymous user in App Router pages that require client side calling
  • Dark mode theme flash on initial navigation
  • Favicon loading on each navigation (and is not cached)

Please make sure these boxes are checked before submitting your pull request - thanks!

  • Run the unit tests with yarn test to make sure you didn't break anything
  • Add or update any needed documentation to the repo
  • Format the title like "feat: [new feature short description]". Title must follow the Conventional Commit Specification(https://www.conventionalcommits.org/en/v1.0.0/).
  • Linked all relevant issues
  • Include screenshot(s) showing how this pull request works and fixes the issue(s)

Feeds loading states

On filter change
image

On navigation
image

Page generated
image

@Alessandro100 Alessandro100 self-assigned this Feb 27, 2026
@vercel
Copy link

vercel bot commented Feb 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mobilitydatabase-web Ready Ready Preview, Comment Feb 27, 2026 4:16pm

Request Review

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves UX and continues the App Router migration by moving the Feeds search + several static pages off the legacy React Router setup, adding SWR-based client caching, and introducing better loading states for feeds navigation and detail pages.

Changes:

  • Migrates /feeds search to App Router and replaces Redux-based fetching with SWR (30-minute client cache).
  • Adds new statically generated App Router routes for FAQ/contact/policy/validator pages and App Router redirects for legacy /feeds/{type} URLs.
  • Improves UX with skeleton/loading indicators, “page generated at” display, and fixes auth unsubscribe/broadcast edge cases.

Reviewed changes

Copilot reviewed 37 out of 38 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
yarn.lock Adds SWR dependency lock entry.
package.json Adds swr dependency.
src/app/styles/PageLayout.style.ts Makes container position: relative (supports overlay/progress positioning).
src/app/store/saga/auth-saga.ts Fixes logout flow (cookie clearing + broadcast guard + correct failure action).
src/app/screens/TermsAndConditions.tsx Marks screen as client component.
src/app/screens/PrivacyPolicy.tsx Marks screen as client component.
src/app/screens/GbfsValidator/index.tsx Marks screen as client component.
src/app/screens/Feeds/SearchTable.tsx Adds delayed full-screen loading overlay + transition-based navigation.
src/app/screens/Feeds/SearchTable.spec.tsx Mocks locale-aware router for updated SearchTable behavior.
src/app/screens/Feeds/FeedsScreenSkeleton.tsx Adds Suspense fallback skeleton for /feeds.
src/app/screens/Feeds/AdvancedSearchTable.tsx Adds delayed loading overlay + transition-based navigation; avoids invalid hook usage by passing theme.
src/app/screens/FeedSubmissionFAQ.tsx Marks screen as client component.
src/app/screens/Feed/components/ScrollToTop.tsx Adds scroll-to-top on feed detail mount.
src/app/screens/Feed/components/GbfsVersions.tsx Replaces ContentBox wrapper with Box.
src/app/screens/Feed/FeedView.tsx Moves “generated at” into header area; adds ScrollToTop.
src/app/screens/FAQ.tsx Marks screen as client component.
src/app/screens/ContactUs.tsx Updates Contact link to new repo; marks screen as client component.
src/app/router/Router.tsx Removes legacy React Router routes for pages migrated to App Router.
src/app/context/GbfsAuthProvider.tsx Marks provider as client component.
src/app/components/HeaderMobileDrawer.tsx Fixes mobile logo rendering per theme + spacing tweaks.
src/app/[locale]/terms-and-conditions/page.tsx Adds SSG App Router page for terms.
src/app/[locale]/privacy-policy/page.tsx Adds SSG App Router page for privacy policy.
src/app/[locale]/gbfs-validator/page.tsx Adds SSG App Router page for GBFS validator (wrapped in provider).
src/app/[locale]/feeds/page.tsx Adds App Router /feeds page with Suspense fallback.
src/app/[locale]/feeds/lib/useFeedsSearch.ts Introduces SWR-powered search hook + URL-derived state helpers.
src/app/[locale]/feeds/gtfs_rt/page.tsx Adds App Router redirect for /feeds/gtfs_rt.
src/app/[locale]/feeds/gtfs/page.tsx Adds App Router redirect for /feeds/gtfs.
src/app/[locale]/feeds/gbfs/page.tsx Adds App Router redirect for /feeds/gbfs.
src/app/[locale]/feeds/components/FeedsScreen.tsx Replaces Redux fetching with URL-driven SWR hook + progress UX.
src/app/[locale]/feeds/[feedDataType]/[feedId]/static/loading.tsx Adds loading skeleton for static feed detail route.
src/app/[locale]/feeds/[feedDataType]/[feedId]/static/layout.tsx Removes layout-level data fetching in favor of page-level fetch.
src/app/[locale]/feeds/[feedDataType]/[feedId]/authed/map/page.tsx Uses notFound() instead of rendering “Feed not found”.
src/app/[locale]/feeds/[feedDataType]/[feedId]/authed/loading.tsx Adds loading skeleton for authed feed detail route.
src/app/[locale]/feeds/[feedDataType]/[feedId]/authed/layout.tsx Removes layout-level data fetching in favor of page-level fetch.
src/app/[locale]/faq/page.tsx Adds SSG App Router page for FAQ.
src/app/[locale]/contribute-faq/page.tsx Adds SSG App Router page for contribute FAQ.
src/app/[locale]/contact-us/page.tsx Adds SSG App Router page for contact us.
src/app/App.tsx Fixes Firebase auth listener leak by unsubscribing on unmount.

Applied skill guidance: custom

Comments suppressed due to low confidence (8)

src/app/components/HeaderMobileDrawer.tsx:66

  • Inside <picture>, <source> should use srcSet (not src). With src, the <source> element is ignored and only the <img> fallback is used, which makes the <source> tags misleading. Switch to srcSet (or remove the <source> entirely if it’s not needed).
        {theme.palette.mode === 'light' ? (
          <picture style={{ display: 'flex' }}>
            <source
              src='/assets/MOBILTYDATA_logo_light_blue_M.png'
              type='image/png'
              height={40}
              width={40}
            />
            <img

src/app/[locale]/feeds/[feedDataType]/[feedId]/authed/loading.tsx:6

  • Minor typo in the doc comment: duplicated word "automatically".
 *  Loading page streams content as it becomes available, providing a faster time-to-interactive and better user experience.
 *  NextJs automatically automatically detects user agents to choose between blocking and streaming behavior.
 *  Since streaming is server-rendered, it does not impact SEO

src/app/[locale]/feeds/lib/useFeedsSearch.ts:73

  • page is parsed with Number(...) directly. If o is missing/invalid (e.g. ?o=abc), this becomes NaN, which then breaks offset calculation and can propagate into the Pagination page prop. Consider sanitizing/clamping to a positive integer (fallback to 1 when Number.isFinite fails).
  return {
    searchQuery: searchParams.get('q') ?? '',
    page: searchParams.get('o') !== null ? Number(searchParams.get('o')) : 1,
    feedTypes,

src/app/[locale]/feeds/lib/useFeedsSearch.ts:235

  • When authReady is false, the SWR key is null, so isLoading becomes false and feedsData stays undefined. In FeedsScreen this results in no skeleton/loading state being shown while anonymous Firebase auth is initializing (blank results area). Consider treating !authReady as a loading state (or return authReady from the hook) so the UI can render the existing skeleton/progress until SWR can fetch.
  const authReady = useFirebaseAuthReady();
  const { cache } = useSWRConfig();
  const derivedSearchParams = deriveSearchParams(searchParams);
  const key = authReady ? buildSwrKey(derivedSearchParams) : null;

  const cachedState = key !== null ? cache.get(key) : undefined;
  const hasCachedDataForKey =
    cachedState !== undefined &&
    typeof cachedState === 'object' &&
    cachedState !== null &&
    'data' in cachedState &&
    cachedState.data !== undefined;

  const {
    data,
    error,
    isLoading,
    isValidating: swrIsValidating,
  } = useSWR<AllFeedsType | undefined>(
    key,
    async () => await feedsFetcher(derivedSearchParams),
    {
      // Keep previous data visible while revalidating (no flash to skeleton)
      keepPreviousData: true,
      // Don't refetch on window focus for search results
      revalidateOnFocus: false,
      // Deduplicate identical requests within 2 seconds
      dedupingInterval: 2000,
    },
  );

  return {
    feedsData: data,
    // True only on first load (no cached data yet)
    isLoading: isLoading && data === undefined,
    // True when SWR is fetching and this key has no cached data yet.
    // This avoids showing loading UI when navigating back/forward to a cached search.
    isValidating: swrIsValidating && !hasCachedDataForKey,
    isError: error !== undefined,
    searchLimit: SEARCH_LIMIT,
  };

src/app/screens/Feed/components/ScrollToTop.tsx:13

  • window.scrollTo only supports behavior: 'auto' | 'smooth'. Using 'instant' is non-standard and may be ignored by browsers, leading to inconsistent scroll restoration. Use behavior: 'auto' (or omit behavior) for an immediate jump to top.
export default function ScrollToTop(): null {
  useEffect(() => {
    window.scrollTo({ top: 0, behavior: 'instant' });
  }, []);

src/app/screens/Feeds/SearchTable.tsx:188

  • New navigation behavior was added (preventDefault + startTransition + locale-aware router.push) but there are no unit tests asserting it. Since this is easy to regress (e.g. modifier clicks should not be prevented, push should be called with the right href), consider adding tests around the click handler behavior.
    src/app/screens/Feeds/AdvancedSearchTable.tsx:255
  • This component now intercepts navigation (preventDefault + startTransition + router.push) and changes UI state (showLoading / opacity). There are currently no unit tests for AdvancedSearchTable, so regressions here may go unnoticed. Consider adding a basic test that verifies the click handler calls router.push and doesn’t intercept modifier/middle clicks.
              sx={{ p: 1, opacity: showLoading || isLoadingFeeds ? 0.7 : 1 }}
              component={NextLinkComposed}
              href={`/feeds/${feed.data_type}/${feed.id}`}
              prefetch={false}
              onClick={(e) => {
                // Navigation to Feed Detail Page can have a delay
                // Show loading state to ease transition
                // This will be further reviewed
                if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1)
                  return;
                e.preventDefault();
                startTransition(() => {
                  router.push(`/feeds/${feed.data_type}/${feed.id}`);
                });
              }}

src/app/[locale]/feeds/[feedDataType]/[feedId]/static/loading.tsx:6

  • Minor typo in the doc comment: duplicated word "automatically".
 *  Loading page streams content as it becomes available, providing a faster time-to-interactive and better user experience.
 *  NextJs automatically automatically detects user agents to choose between blocking and streaming behavior.
 *  Since streaming is server-rendered, it does not impact SEO

@github-actions
Copy link

*Lighthouse ran on https://mobilitydatabase-a1reuktux-mobility-data.vercel.app/ * (Desktop)
⚡️ HTML Report Lighthouse report for the changes in this PR:

Performance Accessibility Best Practices SEO
🟢 90 🟢 96 🟠 78 🟢 100

*Lighthouse ran on https://mobilitydatabase-a1reuktux-mobility-data.vercel.app/feeds * (Desktop)
⚡️ HTML Report Lighthouse report for the changes in this PR:

Performance Accessibility Best Practices SEO
🟢 93 🟠 87 🟠 74 🟢 100

*Lighthouse ran on https://mobilitydatabase-a1reuktux-mobility-data.vercel.app/feeds/gtfs/mdb-2126 * (Desktop)
⚡️ HTML Report Lighthouse report for the changes in this PR:

Performance Accessibility Best Practices SEO
🔴 32 🟢 94 🟠 74 🟢 100

*Lighthouse ran on https://mobilitydatabase-a1reuktux-mobility-data.vercel.app/feeds/gtfs_rt/mdb-2585 * (Desktop)
⚡️ HTML Report Lighthouse report for the changes in this PR:

Performance Accessibility Best Practices SEO
🟠 88 🟠 83 🟠 78 🟢 100

*Lighthouse ran on https://mobilitydatabase-a1reuktux-mobility-data.vercel.app/gbfs/gbfs-flamingo_porirua * (Desktop)
⚡️ HTML Report Lighthouse report for the changes in this PR:

Performance Accessibility Best Practices SEO
🟢 92 🟢 96 🟠 74 🟢 100

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.

SSR: Legacy Router Migration Split 1 (SSG + Feeds Search)

2 participants