From b067f0d29a7054762562d2ea7820b637ce044ed7 Mon Sep 17 00:00:00 2001 From: Alban Bailly <130582365+abailly-akamai@users.noreply.github.com> Date: Thu, 12 Jun 2025 10:58:34 -0400 Subject: [PATCH 01/52] tech-stories - [M3-10109]: Reroute Profile (#12338) * Initial commit - save progress * fix units * fix unit * hooks updates * fix e2e * fix unit * Added changeset: Reroute Profile * make sure EmailBounce is safe for now --- .../pr-12338-tech-stories-1749502229449.md | 5 + .../account/user-verification-banner.spec.ts | 10 +- packages/manager/eslint.config.js | 1 + packages/manager/src/MainContent.tsx | 6 - .../src/components/NavTabs/NavTabs.test.ts | 36 ------ .../src/components/NavTabs/NavTabs.tsx | 109 ------------------ packages/manager/src/factories/devices.ts | 12 ++ .../GlobalNotifications/EmailBounce.tsx | 4 +- .../VerificationDetailsBanner.test.tsx | 7 +- .../VerificationDetailsBanner.tsx | 7 +- .../Profile/APITokens/APITokenTable.tsx | 27 +++-- .../features/Profile/APITokens/APITokens.tsx | 5 - .../AuthenticationSettings.test.tsx | 4 +- .../AuthenticationSettings.tsx | 22 +--- .../AuthenticationSettings/TrustedDevices.tsx | 43 ++++--- .../DisplaySettings/DisplaySettings.test.tsx | 6 +- .../DisplaySettings/DisplaySettings.tsx | 5 - .../DisplaySettings/EmailForm.test.tsx | 19 +-- .../Profile/DisplaySettings/EmailForm.tsx | 8 +- .../Profile/LishSettings/LishSettings.tsx | 5 - .../OAuthClients/OAuthClients.test.tsx | 6 +- .../Profile/OAuthClients/OAuthClients.tsx | 32 ++--- .../manager/src/features/Profile/Profile.tsx | 77 ++++++++----- .../features/Profile/Referrals/Referrals.tsx | 5 - .../src/features/Profile/SSHKeys/SSHKeys.tsx | 5 - .../features/Profile/Settings/Settings.tsx | 21 ++-- packages/manager/src/mocks/serverHandlers.ts | 7 +- packages/manager/src/routes/index.tsx | 1 + packages/manager/src/routes/profile/index.ts | 67 +++++------ .../src/routes/profile/profileLazyRoutes.ts | 7 ++ 30 files changed, 219 insertions(+), 350 deletions(-) create mode 100644 packages/manager/.changeset/pr-12338-tech-stories-1749502229449.md delete mode 100644 packages/manager/src/components/NavTabs/NavTabs.test.ts delete mode 100644 packages/manager/src/components/NavTabs/NavTabs.tsx create mode 100644 packages/manager/src/factories/devices.ts create mode 100644 packages/manager/src/routes/profile/profileLazyRoutes.ts diff --git a/packages/manager/.changeset/pr-12338-tech-stories-1749502229449.md b/packages/manager/.changeset/pr-12338-tech-stories-1749502229449.md new file mode 100644 index 00000000000..5fdcd8fb03a --- /dev/null +++ b/packages/manager/.changeset/pr-12338-tech-stories-1749502229449.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tech Stories +--- + +Reroute Profile ([#12338](https://github.com/linode/manager/pull/12338)) diff --git a/packages/manager/cypress/e2e/core/account/user-verification-banner.spec.ts b/packages/manager/cypress/e2e/core/account/user-verification-banner.spec.ts index d9de39ecb13..b27943a4e06 100644 --- a/packages/manager/cypress/e2e/core/account/user-verification-banner.spec.ts +++ b/packages/manager/cypress/e2e/core/account/user-verification-banner.spec.ts @@ -79,7 +79,10 @@ describe('User verification banner', () => { .should('be.visible') .should('be.enabled') .click(); - cy.url().should('endWith', `/profile/auth`); + cy.url().should( + 'endWith', + `/profile/auth?focusSecurityQuestions=true&focusTel=false` + ); }); /* @@ -157,7 +160,10 @@ describe('User verification banner', () => { .should('be.visible') .should('be.enabled') .click(); - cy.url().should('endWith', `/profile/auth`); + cy.url().should( + 'endWith', + `/profile/auth?focusSecurityQuestions=true&focusTel=false` + ); }); /* diff --git a/packages/manager/eslint.config.js b/packages/manager/eslint.config.js index ee405783659..13cdfed5f1f 100644 --- a/packages/manager/eslint.config.js +++ b/packages/manager/eslint.config.js @@ -420,6 +420,7 @@ export const baseConfig = [ 'src/features/NodeBalancers/**/*', 'src/features/ObjectStorage/**/*', 'src/features/PlacementGroups/**/*', + 'src/features/Profile/**/*', 'src/features/Search/**/*', 'src/features/TopMenu/SearchBar/**/*', 'src/components/Tag/**/*', diff --git a/packages/manager/src/MainContent.tsx b/packages/manager/src/MainContent.tsx index e0e4f7e30ba..6c1aa081ff9 100644 --- a/packages/manager/src/MainContent.tsx +++ b/packages/manager/src/MainContent.tsx @@ -111,11 +111,6 @@ const LinodesRoutes = React.lazy(() => default: module.LinodesRoutes, })) ); -const Profile = React.lazy(() => - import('src/features/Profile/Profile').then((module) => ({ - default: module.Profile, - })) -); const IAM = React.lazy(() => import('src/features/IAM').then((module) => ({ @@ -294,7 +289,6 @@ export const MainContent = () => { {isIAMEnabled && ( )} - {/** We don't want to break any bookmarks. This can probably be removed eventually. */} diff --git a/packages/manager/src/components/NavTabs/NavTabs.test.ts b/packages/manager/src/components/NavTabs/NavTabs.test.ts deleted file mode 100644 index 9d3c82dff09..00000000000 --- a/packages/manager/src/components/NavTabs/NavTabs.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { getTabMatch } from './NavTabs'; - -import type { NavTab } from './NavTabs'; - -describe('getTabMatch', () => { - const tabs: NavTab[] = [ - { - component: null as any, - routeName: '/databases/1234/backups', - title: 'Backups', - }, - { - component: null as any, - routeName: '/databases/1234/settings', - title: 'Settings', - }, - ]; - - it('returns the index of the matched tab', () => { - expect(getTabMatch(tabs, '/databases/1234/settings')).toEqual({ - idx: 1, - isExact: true, - }); - }); - - it('returns whether the match is exact', () => { - expect(getTabMatch(tabs, '/databases/1234/settings/unknown-path')).toEqual({ - idx: 1, - isExact: false, - }); - }); - - it('returns an index of `-1` if there is no match', () => { - expect(getTabMatch(tabs, '/databases/1234/unknown-path').idx).toBe(-1); - }); -}); diff --git a/packages/manager/src/components/NavTabs/NavTabs.tsx b/packages/manager/src/components/NavTabs/NavTabs.tsx deleted file mode 100644 index 7d534de399d..00000000000 --- a/packages/manager/src/components/NavTabs/NavTabs.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import * as React from 'react'; -import { matchPath, Redirect, useHistory, useLocation } from 'react-router-dom'; - -import { SuspenseLoader } from 'src/components/SuspenseLoader'; -import { SafeTabPanel } from 'src/components/Tabs/SafeTabPanel'; -import { TabPanel } from 'src/components/Tabs/TabPanel'; -import { TabPanels } from 'src/components/Tabs/TabPanels'; -import { Tabs } from 'src/components/Tabs/Tabs'; - -import { TabLinkList } from '../Tabs/TabLinkList'; - -export interface NavTab { - // especially when a component behind a tab performs network requests. - backgroundRendering?: boolean; - component?: - | React.ComponentType - | React.LazyExoticComponent; - render?: JSX.Element; - routeName: string; - // Whether or not this tab should be rendered in the background (even when - // not on screen). Consumers should consider performance implications, - title: string; -} - -export interface NavTabsProps { - navToTabRouteOnChange?: boolean; - tabs: NavTab[]; -} - -export const NavTabs = React.memo((props: NavTabsProps) => { - const history = useHistory(); - const reactRouterLocation = useLocation(); - - const { navToTabRouteOnChange, tabs } = props; - - // Defaults to `true`. - const _navToTabRouteOnChange = navToTabRouteOnChange ?? true; - - const navToURL = (index: number) => { - if (tabs[index]) { - history.push(tabs[index].routeName); - } - }; - - const tabMatch = getTabMatch(tabs, reactRouterLocation.pathname); - - // Redirect to the first tab's route name if the pathname is unknown. - if (tabMatch.idx === -1) { - return ; - } - - // Redirect to the exact route name if the pathname doesn't match precisely. - if (!tabMatch.isExact) { - return ; - } - - return ( - - - }> - - {tabs.map((thisTab, i) => { - if (!thisTab.render && !thisTab.component) { - return null; - } - - const _TabPanelComponent = thisTab.backgroundRendering - ? TabPanel - : SafeTabPanel; - - return ( - <_TabPanelComponent index={i} key={thisTab.routeName}> - {thisTab.component ? ( - - ) : thisTab.render ? ( - thisTab.render - ) : null} - - ); - })} - - - - ); -}); - -// Given tabs and a pathname, return the index of the matched tab, and whether -// or not it's an exact match. If no match is found, the returned index is -1. -export const getTabMatch = (tabs: NavTab[], pathname: string) => { - return tabs.reduce( - (acc, thisTab, i) => { - const match = matchPath(pathname, { - exact: false, - path: thisTab.routeName, - }); - - if (match) { - acc.idx = i; - acc.isExact = match.isExact; - } - - return acc; - }, - { idx: -1, isExact: false } - ); -}; diff --git a/packages/manager/src/factories/devices.ts b/packages/manager/src/factories/devices.ts new file mode 100644 index 00000000000..b6aa847835b --- /dev/null +++ b/packages/manager/src/factories/devices.ts @@ -0,0 +1,12 @@ +import { Factory } from '@linode/utilities'; + +import type { TrustedDevice } from '@linode/api-v4/lib/profile/types'; + +export const trustedDeviceFactory = Factory.Sync.makeFactory({ + created: '2020-01-01T12:00:00', + expiry: '2026-01-01T12:00:00', + id: Factory.each((i) => i), + last_authenticated: '2020-01-01T12:00:00', + last_remote_addr: '127.0.0.1', + user_agent: Factory.each((i) => `test-user-agent-${i}`), +}); diff --git a/packages/manager/src/features/GlobalNotifications/EmailBounce.tsx b/packages/manager/src/features/GlobalNotifications/EmailBounce.tsx index 3da20666274..52d09bacfc2 100644 --- a/packages/manager/src/features/GlobalNotifications/EmailBounce.tsx +++ b/packages/manager/src/features/GlobalNotifications/EmailBounce.tsx @@ -68,9 +68,7 @@ export const EmailBounceNotificationSection = React.memo(() => { )} {userEmailBounceNotification && profileEmailRef && ( - history.push('/profile/display', { focusEmail: true }) - } + changeEmail={() => history.push('/profile/display?focusEmail=true')} confirmEmail={confirmProfileEmail} text={ diff --git a/packages/manager/src/features/GlobalNotifications/VerificationDetailsBanner.test.tsx b/packages/manager/src/features/GlobalNotifications/VerificationDetailsBanner.test.tsx index 5b0eadf9cd3..3e1a4a215c4 100644 --- a/packages/manager/src/features/GlobalNotifications/VerificationDetailsBanner.test.tsx +++ b/packages/manager/src/features/GlobalNotifications/VerificationDetailsBanner.test.tsx @@ -71,9 +71,8 @@ describe('VerificationDetailsBanner', () => { fireEvent.click(getByTestId('confirmButton')); // Ensure that history.push is called with the correct arguments - expect(mockHistory.push).toHaveBeenCalledWith('/profile/auth', { - focusSecurityQuestions: true, - focusTel: false, - }); + expect(mockHistory.push).toHaveBeenCalledWith( + '/profile/auth?focusSecurityQuestions=true&focusTel=false' + ); }); }); diff --git a/packages/manager/src/features/GlobalNotifications/VerificationDetailsBanner.tsx b/packages/manager/src/features/GlobalNotifications/VerificationDetailsBanner.tsx index f0749b2720d..11fffca0719 100644 --- a/packages/manager/src/features/GlobalNotifications/VerificationDetailsBanner.tsx +++ b/packages/manager/src/features/GlobalNotifications/VerificationDetailsBanner.tsx @@ -41,7 +41,12 @@ export const VerificationDetailsBanner = ({