From feef2721e0bd43c3ff8eb435265ca75f5c999f37 Mon Sep 17 00:00:00 2001 From: Alessandro Kreslin Date: Tue, 7 Jul 2026 08:56:38 -0400 Subject: [PATCH 1/8] updated Official Feed badge to allow for community feeds --- messages/en.json | 3 + messages/fr.json | 3 + src/app/components/FeedVerificationChip.tsx | 76 +++++++++++++++++++ src/app/components/OfficialChip.tsx | 43 ----------- src/app/screens/Feed/FeedView.tsx | 6 +- .../Feed/components/DataQualitySummary.tsx | 6 +- src/app/screens/Feeds/AdvancedSearchTable.tsx | 6 +- src/app/screens/Feeds/SearchTable.tsx | 6 +- src/app/styles/VerificationBadge.styles.ts | 9 --- 9 files changed, 92 insertions(+), 66 deletions(-) create mode 100644 src/app/components/FeedVerificationChip.tsx delete mode 100644 src/app/components/OfficialChip.tsx delete mode 100644 src/app/styles/VerificationBadge.styles.ts diff --git a/messages/en.json b/messages/en.json index 081f50eb..61a23057 100644 --- a/messages/en.json +++ b/messages/en.json @@ -246,6 +246,9 @@ "officialFeed": "Official Feed", "officialFeedTooltip": "The transit provider has confirmed this feed should be shared with riders. This has been confirmed either by the transit provider providing the feed on their website or from personalized confirmation with the Mobility Database team.", "officialFeedTooltipShort": "Verified feed: Confirmed by the transit provider or the Mobility Database team for rider use.", + "communityFeed": "Community Feed", + "communityFeedTooltip": "This feed is not created on behalf on the transit authority. It may have been created by a community member or third party.", + "communityFeedTooltipShort": "Community feed: Not officially confirmed by the transit provider.", "seeDetailPageProviders": "See detail page to view {providersCount} others", "openFullQualityReport": "Open Full Quality Report", "subscribe": "Subscribe to get feed update notifications", diff --git a/messages/fr.json b/messages/fr.json index 43b77d6e..9c53de8e 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -246,6 +246,9 @@ "officialFeed": "Official Feed", "officialFeedTooltip": "The transit provider has confirmed this feed should be shared with riders. This has been confirmed either by the transit provider providing the feed on their website or from personalized confirmation with the Mobility Database team.", "officialFeedTooltipShort": "Verified feed: Confirmed by the transit provider or the Mobility Database team for rider use.", + "communityFeed": "Community Feed", + "communityFeedTooltip": "This feed has not been officially confirmed by the transit provider. It may have been created by the community or a third party.", + "communityFeedTooltipShort": "Community feed: Not officially confirmed by the transit provider.", "seeDetailPageProviders": "See detail page to view {providersCount} others", "openFullQualityReport": "Open Full Quality Report", "subscribe": "S'abonner", diff --git a/src/app/components/FeedVerificationChip.tsx b/src/app/components/FeedVerificationChip.tsx new file mode 100644 index 00000000..9cfc770c --- /dev/null +++ b/src/app/components/FeedVerificationChip.tsx @@ -0,0 +1,76 @@ +import { Chip, Tooltip } from '@mui/material'; +import { useTranslations } from 'next-intl'; +import VerifiedIcon from '@mui/icons-material/Verified'; +import GroupsIcon from '@mui/icons-material/Groups'; + +interface FeedVerificationChipProps { + isLongDisplay?: boolean; + status?: boolean; +} + +const officialBadgeStyle = { + background: 'linear-gradient(25deg, var(--mui-palette-primary-light), var(--mui-palette-primary-dark))', + color: 'white', +}; + +export default function FeedVerificationChip({ + isLongDisplay = true, + status, +}: FeedVerificationChipProps): React.ReactElement | null { + const t = useTranslations('feeds'); + + if (status == undefined) { + return null; + } + + if (status === false) { + return isLongDisplay ? ( + + } + label={t('communityFeed')} + variant='filled' + > + + ) : ( + + + + ); + } + + return isLongDisplay ? ( + + } + label={t('officialFeed')} + > + + ) : ( + + + + ); +} diff --git a/src/app/components/OfficialChip.tsx b/src/app/components/OfficialChip.tsx deleted file mode 100644 index fcab85a7..00000000 --- a/src/app/components/OfficialChip.tsx +++ /dev/null @@ -1,43 +0,0 @@ -'use client'; -// For this component to become server we will need 'cssVariables' support in MUI - -import { Chip, Tooltip } from '@mui/material'; -import { useTranslations } from 'next-intl'; -import { verificationBadgeStyle } from '../styles/VerificationBadge.styles'; -import VerifiedIcon from '@mui/icons-material/Verified'; - -interface OfficialChipProps { - isLongDisplay?: boolean; -} - -export default function OfficialChip({ - isLongDisplay = true, -}: OfficialChipProps): React.ReactElement { - const t = useTranslations('feeds'); - return ( - <> - {isLongDisplay ? ( - - } - label={t('officialFeed')} - > - - ) : ( - - ({ - display: 'block', - borderRadius: '50%', - padding: '0.1rem', - ml: 0, - mr: 2, - ...verificationBadgeStyle(theme), - })} - > - - )} - - ); -} diff --git a/src/app/screens/Feed/FeedView.tsx b/src/app/screens/Feed/FeedView.tsx index 202d66d8..2b88a71e 100644 --- a/src/app/screens/Feed/FeedView.tsx +++ b/src/app/screens/Feed/FeedView.tsx @@ -7,7 +7,7 @@ import Typography from '@mui/material/Typography'; // Components import FeedTitle from './components/FeedTitle'; -import OfficialChip from '../../components/OfficialChip'; +import FeedVerificationChip from '../../components/FeedVerificationChip'; import DataQualitySummary from './components/DataQualitySummary'; import FeedSummary from './components/FeedSummary'; import FeedNavigationControls from './components/FeedNavigationControls'; @@ -194,14 +194,14 @@ export default async function FeedView({ {feed?.data_type === 'gtfs' && ( )} {feed?.data_type === 'gtfs_rt' && feed.official === true && ( - + )} diff --git a/src/app/screens/Feed/components/DataQualitySummary.tsx b/src/app/screens/Feed/components/DataQualitySummary.tsx index 36f80819..5cb9a33a 100644 --- a/src/app/screens/Feed/components/DataQualitySummary.tsx +++ b/src/app/screens/Feed/components/DataQualitySummary.tsx @@ -5,13 +5,13 @@ import { type components } from '../../../services/feeds/types'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import { WarningContentBox } from '../../../components/WarningContentBox'; import { FeedStatusChip } from '../../../components/FeedStatus'; -import OfficialChip from '../../../components/OfficialChip'; +import FeedVerificationChip from '../../../components/FeedVerificationChip'; import { getTranslations } from 'next-intl/server'; import { getUserRemoteConfigValues } from '../../../../lib/remote-config.server'; export interface DataQualitySummaryProps { feedStatus: components['schemas']['Feed']['status']; - isOfficialFeed: boolean; + isOfficialFeed: boolean | undefined; latestDataset: components['schemas']['GtfsDataset'] | undefined; } @@ -36,7 +36,7 @@ export default async function DataQualitySummary({ {config.enableFeedStatusBadge && ( )} - {isOfficialFeed && } + {latestDataset?.validation_report !== undefined && latestDataset.validation_report !== null && ( <> diff --git a/src/app/screens/Feeds/AdvancedSearchTable.tsx b/src/app/screens/Feeds/AdvancedSearchTable.tsx index 650efadf..ecb0145c 100644 --- a/src/app/screens/Feeds/AdvancedSearchTable.tsx +++ b/src/app/screens/Feeds/AdvancedSearchTable.tsx @@ -23,7 +23,7 @@ import LockIcon from '@mui/icons-material/Lock'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import GtfsRtEntities from './GtfsRtEntities'; import { getEmojiFlag, type TCountryCode } from 'countries-list'; -import OfficialChip from '../../components/OfficialChip'; +import FeedVerificationChip from '../../components/FeedVerificationChip'; import { getFeatureComponentDecorators } from '../../utils/consts'; import PopoverList from './PopoverList'; import ProviderTitle from './ProviderTitle'; @@ -337,9 +337,7 @@ export default function AdvancedSearchTable({ > - {feed.official === true && ( - - )} + {feed.data_type !== 'gbfs' && ( - {feed.official === true && ( - - )} + diff --git a/src/app/styles/VerificationBadge.styles.ts b/src/app/styles/VerificationBadge.styles.ts deleted file mode 100644 index 4d46207e..00000000 --- a/src/app/styles/VerificationBadge.styles.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { type Theme } from '@mui/material'; -import { type SystemStyleObject } from '@mui/system'; - -export const verificationBadgeStyle = ( - theme: Theme, -): SystemStyleObject => ({ - background: `linear-gradient(25deg, ${theme.vars.palette.primary.light}, ${theme.vars.palette.primary.dark})`, - color: 'white', -}); From 31929355d4ffd44dd9689d07c95c526b928c5107 Mon Sep 17 00:00:00 2001 From: Alessandro Kreslin Date: Tue, 7 Jul 2026 09:03:40 -0400 Subject: [PATCH 2/8] lint fix --- src/app/components/FeedVerificationChip.tsx | 5 +++-- src/app/screens/Feed/FeedView.tsx | 4 +++- src/app/screens/Feeds/AdvancedSearchTable.tsx | 5 ++++- src/app/screens/Feeds/SearchTable.tsx | 5 ++++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/app/components/FeedVerificationChip.tsx b/src/app/components/FeedVerificationChip.tsx index 9cfc770c..188064aa 100644 --- a/src/app/components/FeedVerificationChip.tsx +++ b/src/app/components/FeedVerificationChip.tsx @@ -9,7 +9,8 @@ interface FeedVerificationChipProps { } const officialBadgeStyle = { - background: 'linear-gradient(25deg, var(--mui-palette-primary-light), var(--mui-palette-primary-dark))', + background: + 'linear-gradient(25deg, var(--mui-palette-primary-light), var(--mui-palette-primary-dark))', color: 'white', }; @@ -23,7 +24,7 @@ export default function FeedVerificationChip({ return null; } - if (status === false) { + if (!status) { return isLongDisplay ? ( - + )} diff --git a/src/app/screens/Feeds/AdvancedSearchTable.tsx b/src/app/screens/Feeds/AdvancedSearchTable.tsx index ecb0145c..0f23bb6b 100644 --- a/src/app/screens/Feeds/AdvancedSearchTable.tsx +++ b/src/app/screens/Feeds/AdvancedSearchTable.tsx @@ -337,7 +337,10 @@ export default function AdvancedSearchTable({ > - + {feed.data_type !== 'gbfs' && ( - + From 6b46f6969f3414a9044ebdb11637bacebaee12ee Mon Sep 17 00:00:00 2001 From: Alessandro Kreslin Date: Tue, 7 Jul 2026 09:49:45 -0400 Subject: [PATCH 3/8] spelling change --- messages/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/en.json b/messages/en.json index 61a23057..aede4b21 100644 --- a/messages/en.json +++ b/messages/en.json @@ -247,7 +247,7 @@ "officialFeedTooltip": "The transit provider has confirmed this feed should be shared with riders. This has been confirmed either by the transit provider providing the feed on their website or from personalized confirmation with the Mobility Database team.", "officialFeedTooltipShort": "Verified feed: Confirmed by the transit provider or the Mobility Database team for rider use.", "communityFeed": "Community Feed", - "communityFeedTooltip": "This feed is not created on behalf on the transit authority. It may have been created by a community member or third party.", + "communityFeedTooltip": "This feed is not created on behalf of the transit authority. It may have been created by a community member or third party.", "communityFeedTooltipShort": "Community feed: Not officially confirmed by the transit provider.", "seeDetailPageProviders": "See detail page to view {providersCount} others", "openFullQualityReport": "Open Full Quality Report", From e475fdd1ef54cdb55cb4de1ec29477db4fe2085c Mon Sep 17 00:00:00 2001 From: Alessandro Kreslin Date: Tue, 7 Jul 2026 09:50:01 -0400 Subject: [PATCH 4/8] logic fix --- src/app/screens/Feed/FeedView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/screens/Feed/FeedView.tsx b/src/app/screens/Feed/FeedView.tsx index eb349364..45803317 100644 --- a/src/app/screens/Feed/FeedView.tsx +++ b/src/app/screens/Feed/FeedView.tsx @@ -199,7 +199,7 @@ export default async function FeedView({ /> )} - {feed?.data_type === 'gtfs_rt' && feed.official === true && ( + {feed?.data_type === 'gtfs_rt' && feed.official != null && ( Date: Tue, 7 Jul 2026 09:50:19 -0400 Subject: [PATCH 5/8] verified feed component is client --- src/app/components/FeedVerificationChip.tsx | 30 ++++++++++++--------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/app/components/FeedVerificationChip.tsx b/src/app/components/FeedVerificationChip.tsx index 188064aa..1e266e59 100644 --- a/src/app/components/FeedVerificationChip.tsx +++ b/src/app/components/FeedVerificationChip.tsx @@ -1,3 +1,4 @@ +'use client'; import { Chip, Tooltip } from '@mui/material'; import { useTranslations } from 'next-intl'; import VerifiedIcon from '@mui/icons-material/Verified'; @@ -8,11 +9,6 @@ interface FeedVerificationChipProps { status?: boolean; } -const officialBadgeStyle = { - background: - 'linear-gradient(25deg, var(--mui-palette-primary-light), var(--mui-palette-primary-dark))', - color: 'white', -}; export default function FeedVerificationChip({ isLongDisplay = true, @@ -28,6 +24,7 @@ export default function FeedVerificationChip({ return isLongDisplay ? ( } label={t('communityFeed')} @@ -37,16 +34,17 @@ export default function FeedVerificationChip({ ) : ( ({ display: 'block', ml: 0, mr: 2, opacity: 0.6, - backgroundColor: 'var(--mui-palette-grey-400)', - color: 'var(--mui-palette-text-primary)', + backgroundColor: theme.vars.palette.grey[400], + color: theme.vars.palette.text.primary, borderRadius: '50%', padding: '0.2rem', - }} + })} > ); @@ -55,7 +53,11 @@ export default function FeedVerificationChip({ return isLongDisplay ? ( ({ + background: `linear-gradient(25deg, ${theme.vars.palette.primary.light}, ${theme.vars.palette.primary.dark})`, + color: 'white', + })} icon={} label={t('officialFeed')} > @@ -63,14 +65,16 @@ export default function FeedVerificationChip({ ) : ( ({ display: 'block', borderRadius: '50%', padding: '0.1rem', ml: 0, mr: 2, - ...officialBadgeStyle, - }} + background: `linear-gradient(25deg, ${theme.vars.palette.primary.light}, ${theme.vars.palette.primary.dark})`, + color: 'white', + })} > ); From 5156bb6bd08206b63b78c186ddfefdd5be2b44b4 Mon Sep 17 00:00:00 2001 From: Alessandro Kreslin Date: Tue, 7 Jul 2026 09:50:30 -0400 Subject: [PATCH 6/8] tests for verified feed badge --- .../components/FeedVerificationChip.spec.tsx | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/app/components/FeedVerificationChip.spec.tsx diff --git a/src/app/components/FeedVerificationChip.spec.tsx b/src/app/components/FeedVerificationChip.spec.tsx new file mode 100644 index 00000000..82faa3e3 --- /dev/null +++ b/src/app/components/FeedVerificationChip.spec.tsx @@ -0,0 +1,76 @@ +import React, { type JSX } from 'react'; +import { render, screen } from '@testing-library/react'; +import { ThemeProvider } from '@mui/material'; +import { theme } from '../Theme'; +import FeedVerificationChip from './FeedVerificationChip'; + +// next-intl is globally mocked in setupTests.ts: useTranslations returns (key) => key + +function wrapper({ children }: { children: React.ReactNode }): JSX.Element { + return {children}; +} + +describe('FeedVerificationChip', () => { + describe('when status is undefined', () => { + it('renders nothing', () => { + const { container } = render(, { wrapper }); + expect(container).toBeEmptyDOMElement(); + }); + }); + + describe('when status is true (official feed)', () => { + it('renders the official chip with label in long display mode (default)', () => { + render(, { wrapper }); + expect(screen.getByTestId('official-feed-chip')).toBeInTheDocument(); + expect(screen.getByText('officialFeed')).toBeInTheDocument(); + }); + + it('renders the official icon in short display mode', () => { + render(, { wrapper }); + expect(screen.getByTestId('official-feed-icon')).toBeInTheDocument(); + expect(screen.queryByTestId('official-feed-chip')).not.toBeInTheDocument(); + }); + }); + + describe('when status is false (community feed)', () => { + it('renders the community chip with label in long display mode (default)', () => { + render(, { wrapper }); + expect(screen.getByTestId('community-feed-chip')).toBeInTheDocument(); + expect(screen.getByText('communityFeed')).toBeInTheDocument(); + }); + + it('renders the community icon in short display mode', () => { + render(, { wrapper }); + expect(screen.getByTestId('community-feed-icon')).toBeInTheDocument(); + expect(screen.queryByTestId('community-feed-chip')).not.toBeInTheDocument(); + }); + }); + + describe('long display mode (isLongDisplay=true)', () => { + it('renders official chip — not icon — for status=true', () => { + render(, { wrapper }); + expect(screen.getByTestId('official-feed-chip')).toBeInTheDocument(); + expect(screen.queryByTestId('official-feed-icon')).not.toBeInTheDocument(); + }); + + it('renders community chip — not icon — for status=false', () => { + render(, { wrapper }); + expect(screen.getByTestId('community-feed-chip')).toBeInTheDocument(); + expect(screen.queryByTestId('community-feed-icon')).not.toBeInTheDocument(); + }); + }); + + describe('short display mode (isLongDisplay=false)', () => { + it('renders official icon — not chip — for status=true', () => { + render(, { wrapper }); + expect(screen.getByTestId('official-feed-icon')).toBeInTheDocument(); + expect(screen.queryByTestId('official-feed-chip')).not.toBeInTheDocument(); + }); + + it('renders community icon — not chip — for status=false', () => { + render(, { wrapper }); + expect(screen.getByTestId('community-feed-icon')).toBeInTheDocument(); + expect(screen.queryByTestId('community-feed-chip')).not.toBeInTheDocument(); + }); + }); +}); From c1a2ce7ac033a1d511dab793302699a75fef86d7 Mon Sep 17 00:00:00 2001 From: Alessandro Kreslin Date: Tue, 7 Jul 2026 09:57:11 -0400 Subject: [PATCH 7/8] lint fix --- .../components/FeedVerificationChip.spec.tsx | 48 ++++++++++++++----- src/app/components/FeedVerificationChip.tsx | 1 - 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/app/components/FeedVerificationChip.spec.tsx b/src/app/components/FeedVerificationChip.spec.tsx index 82faa3e3..07ada921 100644 --- a/src/app/components/FeedVerificationChip.spec.tsx +++ b/src/app/components/FeedVerificationChip.spec.tsx @@ -26,9 +26,13 @@ describe('FeedVerificationChip', () => { }); it('renders the official icon in short display mode', () => { - render(, { wrapper }); + render(, { + wrapper, + }); expect(screen.getByTestId('official-feed-icon')).toBeInTheDocument(); - expect(screen.queryByTestId('official-feed-chip')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('official-feed-chip'), + ).not.toBeInTheDocument(); }); }); @@ -40,37 +44,57 @@ describe('FeedVerificationChip', () => { }); it('renders the community icon in short display mode', () => { - render(, { wrapper }); + render(, { + wrapper, + }); expect(screen.getByTestId('community-feed-icon')).toBeInTheDocument(); - expect(screen.queryByTestId('community-feed-chip')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('community-feed-chip'), + ).not.toBeInTheDocument(); }); }); describe('long display mode (isLongDisplay=true)', () => { it('renders official chip — not icon — for status=true', () => { - render(, { wrapper }); + render(, { + wrapper, + }); expect(screen.getByTestId('official-feed-chip')).toBeInTheDocument(); - expect(screen.queryByTestId('official-feed-icon')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('official-feed-icon'), + ).not.toBeInTheDocument(); }); it('renders community chip — not icon — for status=false', () => { - render(, { wrapper }); + render(, { + wrapper, + }); expect(screen.getByTestId('community-feed-chip')).toBeInTheDocument(); - expect(screen.queryByTestId('community-feed-icon')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('community-feed-icon'), + ).not.toBeInTheDocument(); }); }); describe('short display mode (isLongDisplay=false)', () => { it('renders official icon — not chip — for status=true', () => { - render(, { wrapper }); + render(, { + wrapper, + }); expect(screen.getByTestId('official-feed-icon')).toBeInTheDocument(); - expect(screen.queryByTestId('official-feed-chip')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('official-feed-chip'), + ).not.toBeInTheDocument(); }); it('renders community icon — not chip — for status=false', () => { - render(, { wrapper }); + render(, { + wrapper, + }); expect(screen.getByTestId('community-feed-icon')).toBeInTheDocument(); - expect(screen.queryByTestId('community-feed-chip')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('community-feed-chip'), + ).not.toBeInTheDocument(); }); }); }); diff --git a/src/app/components/FeedVerificationChip.tsx b/src/app/components/FeedVerificationChip.tsx index 1e266e59..c62e84fd 100644 --- a/src/app/components/FeedVerificationChip.tsx +++ b/src/app/components/FeedVerificationChip.tsx @@ -9,7 +9,6 @@ interface FeedVerificationChipProps { status?: boolean; } - export default function FeedVerificationChip({ isLongDisplay = true, status, From 261abd44c2127493ed03233790c6759895324d4e Mon Sep 17 00:00:00 2001 From: Alessandro Kreslin Date: Wed, 8 Jul 2026 08:36:36 -0400 Subject: [PATCH 8/8] wording change --- messages/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/en.json b/messages/en.json index aede4b21..cbe121ed 100644 --- a/messages/en.json +++ b/messages/en.json @@ -248,7 +248,7 @@ "officialFeedTooltipShort": "Verified feed: Confirmed by the transit provider or the Mobility Database team for rider use.", "communityFeed": "Community Feed", "communityFeedTooltip": "This feed is not created on behalf of the transit authority. It may have been created by a community member or third party.", - "communityFeedTooltipShort": "Community feed: Not officially confirmed by the transit provider.", + "communityFeedTooltipShort": "Community feed: Created by a third party unaffiliated with the transit provider.", "seeDetailPageProviders": "See detail page to view {providersCount} others", "openFullQualityReport": "Open Full Quality Report", "subscribe": "Subscribe to get feed update notifications",