Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,6 @@ export default tseslint.config(
ignoreRestSiblings: true,
},
],
// TODO: hooks called inside render callbacks, incorrect hook usage patterns,
// and missing/extra effect dependencies — to be fixed in a separate ticket.
'react-hooks/rules-of-hooks': 'off',
'react-hooks/refs': 'off',
'react-hooks/set-state-in-render': 'off',
'react-hooks/set-state-in-effect': 'off',
'react-hooks/immutability': 'off',
'react-hooks/exhaustive-deps': 'off',
// TypeScript handles these; disable the core JS versions.
Expand All @@ -61,10 +55,15 @@ export default tseslint.config(
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-enum-comparison': 'off',
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
'@typescript-eslint/no-redundant-type-constituents': 'off',
},
},
{
// Test files: `async` test callbacks and `act(async () => {})` wrappers are
// idiomatic even when they contain no `await` (async `act` flushes the
// microtask queue). Relaxing require-await here avoids churn in test infra.
files: ['**/*.spec.{ts,tsx}', '**/*.test.{ts,tsx}'],
rules: {
'@typescript-eslint/require-await': 'off',
'@typescript-eslint/no-misused-promises': 'off',
},
},
prettierRecommended,
Expand Down
16 changes: 8 additions & 8 deletions src/app/[locale]/account/AccountGeneral.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export default function AccountGeneral(): React.ReactElement {
draftIsRegisteredToReceiveAPIAnnouncements,
setDraftIsRegisteredToReceiveAPIAnnouncements,
] = React.useState(false);
const [alertSeverity, setAlertSeverity] = React.useState<'success' | 'error'>(
'success',
);

const handleEditClick = (): void => {
setDraftFullName(user?.fullName ?? '');
Expand Down Expand Up @@ -76,14 +79,13 @@ export default function AccountGeneral(): React.ReactElement {
React.useEffect(() => {
if (saveStatus === 'success') {
setIsEditing(false);
setAlertSeverity('success');
} else if (saveStatus === 'fail') {
setAlertSeverity('error');
}
}, [saveStatus]);

// Reference is due to dispatch save status acting faster than the exit animation of the alert, causing a flash of the wrong alert severity. With this reference, the severity will be consistent during the whole display of the alert.
const isSaving = saveStatus === 'loading';
const alertSeverity = React.useRef<'success' | 'error'>('success');
if (saveStatus === 'success') alertSeverity.current = 'success';
if (saveStatus === 'fail') alertSeverity.current = 'error';

return (
<>
Expand All @@ -96,15 +98,13 @@ export default function AccountGeneral(): React.ReactElement {
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
>
<Alert
severity={alertSeverity.current}
severity={alertSeverity}
onClose={() => {
dispatch(saveUserProfileReset());
}}
sx={{ width: '100%' }}
>
{alertSeverity.current === 'success'
? t('saveSuccess')
: t('saveError')}
{alertSeverity === 'success' ? t('saveSuccess') : t('saveError')}
</Alert>
</Snackbar>
<AccountSectionContainer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export default function CompleteRegistration(): React.ReactElement {
validationSchema: CompleteRegistrationSchema,
validateOnChange: isSubmitted,
validateOnBlur: true,
onSubmit: async (values) => {
onSubmit: (values) => {
if (user != null) {
dispatch(
refreshUserInformation({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ export default function FormFirstStep({

return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<form
onSubmit={(e) => {
void handleSubmit(onSubmit)(e);
}}
>
<Grid container direction={'column'} rowSpacing={2}>
<Grid>
<FormControl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ export default function FormFourthStep({

return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<form
onSubmit={(e) => {
void handleSubmit(onSubmit)(e);
}}
>
<Grid container direction={'column'} rowSpacing={2}>
<Grid>
<FormControl component='fieldset' fullWidth>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ export default function FormSecondStep({
return (
<>
<Typography gutterBottom>{t('gtfsScheduleFeed')}</Typography>
<form onSubmit={handleSubmit(onSubmit)}>
<form
onSubmit={(e) => {
void handleSubmit(onSubmit)(e);
}}
>
<Grid container direction={'column'} rowSpacing={2}>
<Grid>
<FormControl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,11 @@ export default function FormSecondStepRT({
>
{t('gtfsRealtimeFeed')}
</Typography>
<form onSubmit={handleSubmit(onSubmit)}>
<form
onSubmit={(e) => {
void handleSubmit(onSubmit)(e);
}}
>
<Grid container direction={'column'} rowSpacing={2}>
<Grid>
<FormControl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ export default function FormThirdStep({

return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<form
onSubmit={(e) => {
void handleSubmit(onSubmit)(e);
}}
>
<Grid container direction={'column'} rowSpacing={2}>
{/* Show required emptyLicenseUsage if official producer and no license provided */}
{isOfficialProducer && noLicenseProvided && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ interface Props {
* from the clean URLs like /feeds/gtfs/mdb-123.
* TODO: Now that legacy catch-all route is removed, change this to a private route `_static` and update proxy and links accordingly.
*/
export default async function StaticFeedLayout({
export default function StaticFeedLayout({
children,
params,
}: Props): Promise<React.ReactElement> {
}: Props): React.ReactElement {
return <>{children}</>;
}
2 changes: 1 addition & 1 deletion src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default async function LocaleLayout({
}

// At this point, locale is guaranteed to be a valid Locale type
const validLocale = locale as Locale;
const validLocale = locale;

// Enable static rendering for this locale
setRequestLocale(validLocale);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,6 @@ const DetailPanel: React.FC<RenderDetailPanelProps> = ({ row }) => {
const theme = useTheme();
const { notices, metrics } = row.original;

if (metrics == null) {
return <div>No metrics available</div>;
}

const chartData = metrics.computed_on.map((date, index) => {
const utcDate = new Date(date).toLocaleDateString('en-CA', {
timeZone: 'UTC',
}); // Converts the date to UTC

return {
date: utcDate,
count: metrics.errors_count[index],
};
});

const domain = [
new Date(chartData[0]?.date ?? '').getTime(),
new Date().getTime(),
];

// Define the columns for the notices table
const columns = useMemo<Array<MRT_ColumnDef<GBFSNotice>>>(
() => [
Expand Down Expand Up @@ -91,6 +71,26 @@ const DetailPanel: React.FC<RenderDetailPanelProps> = ({ row }) => {
muiTableContainerProps: { sx: { maxHeight: '50vh' } },
});

if (metrics == null) {
return <div>No metrics available</div>;
}

const chartData = metrics.computed_on.map((date, index) => {
const utcDate = new Date(date).toLocaleDateString('en-CA', {
timeZone: 'UTC',
}); // Converts the date to UTC

return {
date: utcDate,
count: metrics.errors_count[index],
};
});

const domain = [
new Date(chartData[0]?.date ?? '').getTime(),
new Date().getTime(),
];

return (
<Grid container spacing={3} sx={{ maxWidth: '1200px' }}>
<Grid size={{ xs: 12, md: 8 }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,16 @@ import { setAnalyticsBucketEndpoint } from '../../../../../utils/metricsUtils';
export default function GBFSFeedAnalytics(): React.ReactElement {
const searchParams = useSearchParams();
const { config } = useRemoteConfig();
const [schemaPathFilters, setSchemaPathFilters] = React.useState<string[]>(
[],
);

const versionFilter = searchParams.get('version');
const schemaPathInitFilter = decodeURIComponent(
searchParams.get('schemaPath') ?? '',
);

const [schemaPathFilters, setSchemaPathFilters] = React.useState<string[]>(
schemaPathInitFilter ? [schemaPathInitFilter] : [],
);

const dispatch = useDispatch();
const rawData = useSelector(selectGBFSFeedMetrics);
const status = useSelector(selectGBFSAnalyticsStatus);
Expand Down Expand Up @@ -126,15 +127,6 @@ export default function GBFSFeedAnalytics(): React.ReactElement {
return filters;
}, [versionFilter]);

useMemo(() => {
if (
schemaPathInitFilter != null &&
schemaPathFilterOptions.includes(schemaPathInitFilter)
) {
setSchemaPathFilters([schemaPathInitFilter]);
}
}, [schemaPathInitFilter, schemaPathFilterOptions]);

const columns = useTableColumns();
const csvConfig = mkConfig({
fieldSeparator: ',',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default function GBFSNoticeAnalytics(): React.ReactElement {
const router = useRouter();
const searchParams = useSearchParams();
const noticeCode = searchParams.get('noticeCode');
const theme = useTheme();
const [data, setData] = useState<GBFSNoticeMetrics[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
Expand Down Expand Up @@ -133,7 +134,6 @@ export default function GBFSNoticeAnalytics(): React.ReactElement {
enableStickyFooter: true,
muiTableContainerProps: { sx: { maxHeight: '70vh' } },
renderDetailPanel: ({ row }) => {
const theme = useTheme();
const metrics = row.original;

const chartData = metrics.computed_on.map((date, index) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { GBFS_LINK } from '../../../../../constants/Navigation';

export default function GBFSVersionAnalytics(): React.ReactElement {
const router = useRouter();
const theme = useTheme();
const [data, setData] = useState<GBFSVersionMetrics[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
Expand Down Expand Up @@ -157,7 +158,6 @@ export default function GBFSVersionAnalytics(): React.ReactElement {
enableStickyFooter: true,
muiTableContainerProps: { sx: { maxHeight: '70vh' } },
renderDetailPanel: ({ row }) => {
const theme = useTheme();
const metrics = row.original;

const chartData = metrics.computed_on.map((date, index) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {

export default function GTFSFeatureAnalytics(): React.ReactElement {
const router = useRouter();
const theme = useTheme();
const searchParams = useSearchParams();
const featureName = searchParams.get('featureName');
const [data, setData] = useState<FeatureMetrics[]>([]);
Expand Down Expand Up @@ -221,7 +222,6 @@ export default function GTFSFeatureAnalytics(): React.ReactElement {
enableStickyFooter: true,
muiTableContainerProps: { sx: { maxHeight: '70vh' } },
renderDetailPanel: ({ row }) => {
const theme = useTheme();
const metrics = row.original;

const chartData = metrics.computed_on.map((date, index) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { useRemoteConfig } from '../../../../../context/RemoteConfigProvider';

export default function GTFSNoticeAnalytics(): React.ReactElement {
const router = useRouter();
const theme = useTheme();
const searchParams = useSearchParams();
const noticeCode = searchParams.get('noticeCode');
const [data, setData] = useState<NoticeMetrics[]>([]);
Expand Down Expand Up @@ -193,7 +194,6 @@ export default function GTFSNoticeAnalytics(): React.ReactElement {
enableStickyFooter: true,
muiTableContainerProps: { sx: { maxHeight: '70vh' } },
renderDetailPanel: ({ row }) => {
const theme = useTheme();
const metrics = row.original;

const chartData = metrics.computed_on.map((date, index) => ({
Expand Down
6 changes: 3 additions & 3 deletions src/app/api/revalidate/route.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('GET /api/revalidate', () => {
},
});

const response = await GET(request);
const response = GET(request);
const json = await response.json();

expect(response.status).toBe(500);
Expand All @@ -65,7 +65,7 @@ describe('GET /api/revalidate', () => {
},
});

const response = await GET(request);
const response = GET(request);
const json = await response.json();

expect(response.status).toBe(401);
Expand All @@ -87,7 +87,7 @@ describe('GET /api/revalidate', () => {
},
});

const response = await GET(request);
const response = GET(request);
const json = await response.json();

expect(response.status).toBe(200);
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/revalidate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const defaultRevalidateOptions: RevalidateBody = {
* Vercel automatically passes Authorization: Bearer <CRON_SECRET> with each invocation.
* Configured in vercel.json under "crons" for 4am UTC Monday-Saturday and 7am UTC Sunday.
*/
export async function GET(req: Request): Promise<NextResponse> {
export function GET(req: Request): NextResponse {
const authHeader = req.headers.get('authorization');
const cronSecret = process.env.CRON_SECRET;

Expand Down
3 changes: 2 additions & 1 deletion src/app/api/session/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
}
}

export async function GET(req: NextRequest): Promise<NextResponse> {
export function GET(req: NextRequest): NextResponse {
try {
const cookie = req.cookies.get(COOKIE_NAME)?.value;
if (cookie == null) {
Expand All @@ -89,6 +89,7 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
}

export async function DELETE(req: NextRequest): Promise<NextResponse> {
// Clear the session cookie so that subsequent requests have no session.
const response = NextResponse.json({ status: 'logged_out' });
response.cookies.delete(COOKIE_NAME);
return response;
Expand Down
Loading