Skip to content
Merged
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
139 changes: 139 additions & 0 deletions doc/spec/20260825_2241_upgrade_mui_7x.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Frontend: Upgrade MUI from v6.x to v7.x

## Problem Statement

The frontend currently depends on Material UI v6 (`@mui/material` at `^6.5.0` plus related MUI packages). The project is now on a newer toolchain baseline (Next.js 15, React 19, TypeScript 5.x), and the product has already started consuming MUI v7-compatible packages in a few areas. We need to complete the upgrade to MUI 7.x to stay aligned with the active upstream support window, avoid technical debt, and reduce the risk of future incompatibilities with the rest of the frontend stack.

The upgrade should be treated as a compatibility migration, not a redesign. We need to adopt the v7 API and behavior without changing user-visible functionality, while keeping the app stable across SSR, pages, and shared UI components.

## Related issue

- GitHub issue: #2241
- Official migration guide: https://v7.mui.com/material-ui/migration/upgrade-to-v7/

## Scope

### In scope

- Upgrading the frontend MUI dependencies to v7.x.
- Updating any code that is incompatible with MUI v7 defaults or deprecations.
- Resolving theme, styling, and component API differences introduced by the upgrade.
- Verifying that all frontend linting, type checking, and tests still pass.
- Ensuring pages, forms, navigation, dialogs, and layout components keep their current behavior.

### Out of scope

- Redesigning the UI or product flows.
- Rewriting large portions of the frontend unrelated to MUI compatibility.
- Backend work or any data model changes.
- General React/Next.js modernization beyond what is required to support MUI 7.

## Background and Root Cause

This upgrade is required because the app is pinned to a MUI v6 stack while the ecosystem and other frontend dependencies have moved forward. The risk is not just dependency drift: MUI 7 introduces breaking changes in API usage, styling assumptions, and some slot and theming patterns. If the upgrade is done without a disciplined migration pass, the app will likely fail in compile-time or runtime because of deprecated patterns and changed defaults.

This issue should follow the official MUI V7 migration guidance and adapt only the parts of the app that are incompatible with that major change. The goal is to land a working upgrade that preserves the same UX while removing v6-era compatibility constraints.

## Proposed Solution

1. Upgrade the MUI packages used by the frontend to the v7-compatible release line.
2. Audit the app for usage patterns that are known to break or deprecate in v7.
3. Update theme setup, styled components, and custom wrappers to the v7-compatible patterns.
4. Remove or replace any legacy v6-only usage that is not supported in v7.
5. Run the repo’s frontend validation commands and fix any regressions.

## Detailed Requirements

### Dependency upgrade

- Update the MUI packages in `frontend/package.json` to v7.x versions.
- Ensure the full set of MUI libraries used by the project remains internally consistent:
- `@mui/material`
- `@mui/icons-material`
- `@mui/x-data-grid`
- `@mui/x-date-pickers`
- any other MUI packages imported by the app
- Validate whether `@mui/styles` is still required. If it is not necessary for the app, remove it or replace its usage with emotion-based styling as appropriate.

### Compatibility pass

The migration should include a targeted review of:

- Theme overrides and custom `createTheme` usage.
- Styled components built with `styled(...)` and emotion wrappers.
- Components using old MUI slot APIs or deprecated props.
- Components reading theme or palette values via patterns no longer recommended in v7.
- Cases where `Grid` or layout helpers rely on v6 assumptions.
- Components or helpers that pass `className`, `sx`, `slotProps`, `slot` values, or `components` overrides in ways that changed in v7.

The migration should prefer the current official v7 patterns over maintaining a compatibility layer unless the compatibility layer is necessary to avoid a broad refactor.

### Implementation constraints

- This is a frontend-only change.
- No backend API contract changes are allowed.
- No product requirements or design changes should be bundled into the MUI upgrade.
- Existing URLs, routes, and user flows must remain unchanged.
- The upgrade should not break SSR or client rendering behavior.

## Acceptance Criteria

### Functional / build criteria

- [ ] The frontend dependencies are upgraded to MUI v7.x in `frontend/package.json`.
- [ ] The app builds successfully with the upgraded dependency set.
- [ ] Type checking and linting pass after the migration.
- [ ] Existing frontend tests pass or are updated only where required by v7 compatibility.
- [ ] No user-facing behavior regressions are introduced by the upgrade.

### Code compatibility criteria

- [ ] All code that relies on MUI v6 behavior is reviewed and updated to v7-compatible patterns.
- [ ] Deprecated or removed MUI API usage is replaced with supported v7 equivalents.
- [ ] Theme configuration remains valid under MUI v7.
- [ ] Any legacy usage of `@mui/styles` is removed or reduced to a non-breaking migration path.
- [ ] Custom wrappers and shared components continue to render and behave correctly.

### Regression prevention criteria

- [ ] The app still renders correctly in common pages, forms, dialogs, and navigation flows.
- [ ] No crash occurs during SSR or hydration due to theme or component incompatibility.
- [ ] Layout and visual styling remain acceptable after the upgrade.
- [ ] No unreachable or hidden runtime errors are introduced in the UI by a v7 API change.

## Validation Plan

The following checks must be run before the change is considered complete:

1. `cd frontend && yarn install` (or equivalent lockfile update if needed)
2. `cd frontend && yarn lint`
3. `cd frontend && yarn check-types`
4. `cd frontend && yarn test` (or the project’s relevant frontend test target)
5. `cd frontend && yarn build`

If the project has additional page-level smoke checks, those should be run as part of the migration validation, particularly around shared layout and any frequently used MUI components.

## Risks and Considerations

### Hidden compatibility issues

Some breakage may not appear in unit tests because it will only show up in hydrated pages or in specific UI edge cases. We should therefore prioritize app-wide validation of shared UI components and layouts, not just isolated component tests.

### Theme and styling drift

MUI 7 may change theme semantics or default styling behavior. Custom theme overrides need to be reviewed carefully so that color, spacing, shadows, typography, and component variants remain visually consistent.

### Dependency mismatch with companion packages

`@mui/x-data-grid`, `@mui/x-date-pickers`, and other MUI ecosystem packages should be upgraded together to avoid version skew. Mixed-version MUI packages can lead to subtle runtime issues that are difficult to diagnose later.

## Implementation Notes

- Prefer a careful, incremental migration over a broad rewrite.
- Keep the patch focused on compatibility and behavior preservation.
- If a compatibility issue is discovered in a shared component, fix the shared abstraction rather than patching a single page in isolation.
- Document any intentional exceptions or workarounds in code comments only when they are required for correctness.

## Definition of Done

The issue is complete when all acceptance criteria above are met, the frontend runs cleanly with MUI v7.x, and the project has no unresolved migration breakages in validation or local smoke testing.
8 changes: 4 additions & 4 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@floating-ui/dom": "^1.0.0",
"@mui/icons-material": "^6.5.0",
"@mui/material": "^6.5.0",
"@mui/icons-material": "^7.3.11",
"@mui/material": "^7.3.11",
"@mui/styles": "^6.5.0",
Comment thread
fateme-ramezanpour marked this conversation as resolved.
"@mui/x-data-grid": "^7.0.0",
"@mui/x-date-pickers": "^7.0.0",
"@mui/x-data-grid": "^7.29.13",
"@mui/x-date-pickers": "^7.29.4",
"@next/bundle-analyzer": "^15.5.23",
"@sentry/nextjs": "^10.57.0",
"@svgr/webpack": "^8.1.0",
Expand Down
44 changes: 28 additions & 16 deletions frontend/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import CssBaseline from "@mui/material/CssBaseline";
import { Theme, StyledEngineProvider, ThemeProvider } from "@mui/material/styles";
import { ThemeProvider as StylesThemeProvider } from "@mui/styles";
import { useRouter } from "next/router";
import React, { useEffect, useContext, useState } from "react";
import App from "next/app";
Expand Down Expand Up @@ -315,24 +316,35 @@ function AppContent({
<>
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
{/*
* Feature toggles are opt-in per page via getServerSideProps.
* Pages that need SSR feature toggles should call getFeatureTogglesFromRequest
* from src/hooks/featureToggles.ts and return { featureToggles, environment }
* as props. FeatureToggleProvider picks them up here via pageProps.
* Pages without getServerSideProps will still work but toggles resolve
* client-side only (isEnabled returns the fallback value on first render).
* `@mui/styles` (still used by many components via `makeStyles`) bundles its own
* copy of `@mui/private-theming`, which is a different module instance/version than
* the one used internally by `@mui/material` v7. That means its `useTheme`/`makeStyles`
* can't see the theme provided by the `ThemeProvider` above via React context.
* Nesting `@mui/styles`' own `ThemeProvider` here with the same theme object makes the
* theme available to `@mui/styles` consumers again without having to migrate every
* `makeStyles` usage away from `@mui/styles`.
*/}
<FeatureToggleProvider
initialToggles={pageProps.featureToggles}
environment={pageProps.environment}
>
<UserContext.Provider value={contextValues}>
<Component {...pageProps} />
</UserContext.Provider>
</FeatureToggleProvider>
<StylesThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
{/*
* Feature toggles are opt-in per page via getServerSideProps.
* Pages that need SSR feature toggles should call getFeatureTogglesFromRequest
* from src/hooks/featureToggles.ts and return { featureToggles, environment }
* as props. FeatureToggleProvider picks them up here via pageProps.
* Pages without getServerSideProps will still work but toggles resolve
* client-side only (isEnabled returns the fallback value on first render).
*/}
<FeatureToggleProvider
initialToggles={pageProps.featureToggles}
environment={pageProps.environment}
>
<UserContext.Provider value={contextValues}>
<Component {...pageProps} />
</UserContext.Provider>
</FeatureToggleProvider>
</StylesThemeProvider>
</ThemeProvider>
</StyledEngineProvider>
</>
Expand Down
2 changes: 0 additions & 2 deletions frontend/pages/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,6 @@ export default function Login({ hubThemeData, hubSlug }: LoginProps) {
<CardContent>{getStepContent()}</CardContent>
</Card>
}
leftGridSizes={{ md: 7 }}
rightGridSizes={{ md: 5 }}
image={<CustomAuthImage hubUrl={hubSlug || undefined} texts={texts} />}
/>
)}
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/components/auth/AuthEmailStep.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import { ThemeProvider } from "@mui/material/styles";
import { ThemeProvider as StylesThemeProvider } from "@mui/styles";
import theme from "../../themes/theme";
import UserContext from "../context/UserContext";
import AuthEmailStep from "./AuthEmailStep";
Expand Down Expand Up @@ -42,9 +43,11 @@ function renderAuthEmailStep({
} = {}) {
return render(
<ThemeProvider theme={theme}>
<UserContext.Provider value={makeContextValue(locale) as any}>
<AuthEmailStep onUserStatusDetermined={onUserStatusDetermined} hubUrl={hubUrl} />
</UserContext.Provider>
<StylesThemeProvider theme={theme}>
<UserContext.Provider value={makeContextValue(locale) as any}>
<AuthEmailStep onUserStatusDetermined={onUserStatusDetermined} hubUrl={hubUrl} />
</UserContext.Provider>
</StylesThemeProvider>
</ThemeProvider>
);
}
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/components/auth/AuthForgotPassword.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import { ThemeProvider } from "@mui/material/styles";
import { ThemeProvider as StylesThemeProvider } from "@mui/styles";
import theme from "../../themes/theme";
import UserContext from "../context/UserContext";
import AuthForgotPassword from "./AuthForgotPassword";
Expand Down Expand Up @@ -43,9 +44,11 @@ function renderComponent(props: Partial<typeof defaultProps> & { locale?: "en" |

return render(
<ThemeProvider theme={theme}>
<UserContext.Provider value={makeContextValue(locale) as any}>
<AuthForgotPassword {...mergedProps} />
</UserContext.Provider>
<StylesThemeProvider theme={theme}>
<UserContext.Provider value={makeContextValue(locale) as any}>
<AuthForgotPassword {...mergedProps} />
</UserContext.Provider>
</StylesThemeProvider>
</ThemeProvider>
);
}
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/components/auth/AuthOtp.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import { render, screen, fireEvent, waitFor, act } from "@testing-library/react";
import "@testing-library/jest-dom";
import { ThemeProvider } from "@mui/material/styles";
import { ThemeProvider as StylesThemeProvider } from "@mui/styles";
import theme from "../../themes/theme";
import UserContext from "../context/UserContext";
import AuthOtp from "./AuthOtp";
Expand Down Expand Up @@ -45,9 +46,11 @@ function renderAuthOtp({
} = {}) {
return render(
<ThemeProvider theme={theme}>
<UserContext.Provider value={makeContextValue(locale) as any}>
<AuthOtp email={email} onBack={onBack} onSuccess={onSuccess} hubUrl={hubUrl} />
</UserContext.Provider>
<StylesThemeProvider theme={theme}>
<UserContext.Provider value={makeContextValue(locale) as any}>
<AuthOtp email={email} onBack={onBack} onSuccess={onSuccess} hubUrl={hubUrl} />
</UserContext.Provider>
</StylesThemeProvider>
</ThemeProvider>
);
}
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/components/auth/AuthPasswordLogin.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import { ThemeProvider } from "@mui/material/styles";
import { ThemeProvider as StylesThemeProvider } from "@mui/styles";
import theme from "../../themes/theme";
import UserContext from "../context/UserContext";
import AuthPasswordLogin from "./AuthPasswordLogin";
Expand Down Expand Up @@ -49,9 +50,11 @@ function renderComponent(props: Partial<typeof defaultProps> & { locale?: "en" |

return render(
<ThemeProvider theme={theme}>
<UserContext.Provider value={makeContextValue(locale) as any}>
<AuthPasswordLogin {...mergedProps} />
</UserContext.Provider>
<StylesThemeProvider theme={theme}>
<UserContext.Provider value={makeContextValue(locale) as any}>
<AuthPasswordLogin {...mergedProps} />
</UserContext.Provider>
</StylesThemeProvider>
</ThemeProvider>
);
}
Expand Down
19 changes: 11 additions & 8 deletions frontend/src/components/auth/AuthSignupStep.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import { ThemeProvider } from "@mui/material/styles";
import { ThemeProvider as StylesThemeProvider } from "@mui/styles";
import theme from "../../themes/theme";
import UserContext from "../context/UserContext";
import AuthSignupStep from "./AuthSignupStep";
Expand Down Expand Up @@ -103,14 +104,16 @@ function renderAuthSignupStep({
} = {}) {
return render(
<ThemeProvider theme={theme}>
<UserContext.Provider value={makeContextValue(locale) as any}>
<AuthSignupStep
email={email}
onBack={onBack}
onSignupComplete={onSignupComplete}
hubUrl={hubUrl}
/>
</UserContext.Provider>
<StylesThemeProvider theme={theme}>
<UserContext.Provider value={makeContextValue(locale) as any}>
<AuthSignupStep
email={email}
onBack={onBack}
onSignupComplete={onSignupComplete}
hubUrl={hubUrl}
/>
</UserContext.Provider>
</StylesThemeProvider>
</ThemeProvider>
);
}
Expand Down
23 changes: 13 additions & 10 deletions frontend/src/components/auth/SignupInterestsStep.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import { ThemeProvider } from "@mui/material/styles";
import { ThemeProvider as StylesThemeProvider } from "@mui/styles";
import theme from "../../themes/theme";
import UserContext from "../context/UserContext";
import SignupInterestsStep from "./SignupInterestsStep";
Expand Down Expand Up @@ -111,16 +112,18 @@ function renderSignupInterestsStep({
} = {}) {
return render(
<ThemeProvider theme={theme}>
<UserContext.Provider value={makeContextValue(locale) as any}>
<SignupInterestsStep
email={email}
onSubmit={onSubmit}
onBack={onBack}
hubUrl={hubUrl}
isLoading={isLoading}
errorMessage={errorMessage}
/>
</UserContext.Provider>
<StylesThemeProvider theme={theme}>
<UserContext.Provider value={makeContextValue(locale) as any}>
<SignupInterestsStep
email={email}
onSubmit={onSubmit}
onBack={onBack}
hubUrl={hubUrl}
isLoading={isLoading}
errorMessage={errorMessage}
/>
</UserContext.Provider>
</StylesThemeProvider>
</ThemeProvider>
);
}
Expand Down
Loading
Loading