Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Tech Stories
---

Refactor Domains Routing (Tanstack Router) ([#11418](https://github.com/linode/manager/pull/11418))
1 change: 1 addition & 0 deletions packages/manager/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ module.exports = {
files: [
// for each new features added to the migration router, add its directory here
'src/features/Betas/**/*',
'src/features/Domains/**/*',
'src/features/Volumes/**/*',
],
rules: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ describe('Clone a Domain', () => {
.click();
});
// After cloning a Domain, the user is redirected to the new Domain's details page
cy.url().should('endWith', 'domains');
cy.url().should('match', /\/domains\/\d+$/);

// Confirm that domain is cloned and cloned domains contain the same records as the original Domain.
cy.visitWithLogin('/domains');
Expand Down
6 changes: 0 additions & 6 deletions packages/manager/src/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,6 @@ const LinodesRoutes = React.lazy(() =>
default: module.LinodesRoutes,
}))
);
const Domains = React.lazy(() =>
import('src/features/Domains').then((module) => ({
default: module.DomainsRoutes,
}))
);
const Images = React.lazy(() => import('src/features/Images'));
const Kubernetes = React.lazy(() =>
import('src/features/Kubernetes').then((module) => ({
Expand Down Expand Up @@ -343,7 +338,6 @@ export const MainContent = () => {
component={NodeBalancers}
path="/nodebalancers"
/>
<Route component={Domains} path="/domains" />
<Route component={Managed} path="/managed" />
<Route component={Longview} path="/longview" />
<Route component={Images} path="/images" />
Expand Down
2 changes: 1 addition & 1 deletion packages/manager/src/features/Betas/BetaSignup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useCreateAccountBetaMutation } from 'src/queries/account/betas';
import { useBetaQuery } from 'src/queries/betas';

export const BetaSignup = () => {
const betaAgreement = `# Early Adopter Testing Program
const betaAgreement = `### Early Adopter Testing Program

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This was caught by my route test once I made it more resilient. It caught two H1 headers on the page πŸŽ‰

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice, solid test!

This Early Adopter Testing Program Service Level Agreement (the β€œEAP”) is between Linode LLC (β€œLinode”) and
you, the customer who requests access and participation (the β€œParticipant”) to the Linode Early Access Program
(the β€œProgram”). This EAP is attached to and amends the master services agreement between you and Linode
Expand Down
19 changes: 14 additions & 5 deletions packages/manager/src/features/Domains/CloneDomainDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
RadioGroup,
TextField,
} from '@linode/ui';
import { useNavigate } from '@tanstack/react-router';
import { useFormik } from 'formik';
import React from 'react';
import { useHistory } from 'react-router-dom';

import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { Drawer } from 'src/components/Drawer';
Expand All @@ -18,12 +18,13 @@ import type { Domain } from '@linode/api-v4';

interface CloneDomainDrawerProps {
domain: Domain | undefined;
isFetching: boolean;
onClose: () => void;
open: boolean;
}

export const CloneDomainDrawer = (props: CloneDomainDrawerProps) => {
const { domain, onClose: _onClose, open } = props;
const { domain, isFetching, onClose: _onClose, open } = props;

const { data: profile } = useProfile();
const { data: grants } = useGrants();
Expand All @@ -32,14 +33,17 @@ export const CloneDomainDrawer = (props: CloneDomainDrawerProps) => {
domain?.id ?? 0
);

const history = useHistory();
const navigate = useNavigate();

const formik = useFormik<{ domain: string }>({
initialValues: { domain: '' },
onSubmit: async (values) => {
const newDomain = await cloneDomain(values);
history.push(`/domains/${newDomain.id}`);
onClose();
navigate({
params: { domainId: newDomain.id },
to: '/domains/$domainId',
});
},
});

Expand All @@ -52,7 +56,12 @@ export const CloneDomainDrawer = (props: CloneDomainDrawerProps) => {
const noPermission = profile?.restricted && !grants?.global.add_domains;

return (
<Drawer onClose={onClose} open={open} title="Clone Domain">
<Drawer
isFetching={isFetching}
onClose={onClose}
open={open}
title="Clone Domain"
>
{noPermission && (
<Notice variant="error">
You do not have permission to create new Domains.
Expand Down
22 changes: 11 additions & 11 deletions packages/manager/src/features/Domains/CreateDomain/CreateDomain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import {
import { createDomainSchema } from '@linode/validation/lib/domains.schema';
import { styled } from '@mui/material/styles';
import Grid from '@mui/material/Unstable_Grid2';
import { createLazyRoute } from '@tanstack/react-router';
import { useNavigate } from '@tanstack/react-router';
import { useFormik } from 'formik';
import { path } from 'ramda';
import * as React from 'react';
import { useHistory } from 'react-router-dom';

import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { DocumentTitleSegment } from 'src/components/DocumentTitle';
Expand Down Expand Up @@ -46,6 +45,7 @@ import type {
} from '@linode/api-v4/lib/domains';
import type { NodeBalancer } from '@linode/api-v4/lib/nodebalancers';
import type { APIError } from '@linode/api-v4/lib/types';
import type { DomainState } from 'src/routes/domains';
import type { ExtendedIP } from 'src/utilities/ipUtils';

interface DefaultRecordsSetting {
Expand All @@ -65,7 +65,7 @@ export const CreateDomain = () => {
// of the payload and must be handled separately.
const [errors, setErrors] = React.useState<APIError[] | undefined>(undefined);

const history = useHistory();
const navigate = useNavigate();

const defaultRecords: DefaultRecordsSetting[] = [
{
Expand Down Expand Up @@ -127,20 +127,24 @@ export const CreateDomain = () => {
const isCreatingPrimaryDomain = values.type === 'master';
const isCreatingSecondaryDomain = values.type === 'slave';

const redirect = (id: '' | number, state?: Record<string, string>) => {
const redirect = (id: null | number, state?: DomainState) => {
const returnPath = !!id ? `/domains/${id}` : '/domains';
history.push(returnPath, state);
navigate({
params: { domainId: Number(id) },
state: (prev) => ({ ...prev, ...state }),
to: returnPath,
});
};

const redirectToLandingOrDetail = (
type: 'master' | 'slave',
domainID: number,
state: Record<string, string> = {}
state: DomainState = {}
) => {
if (type === 'master' && domainID) {
redirect(domainID, state);
} else {
redirect('', state);
redirect(null, state);
}
};

Expand Down Expand Up @@ -449,10 +453,6 @@ export const CreateDomain = () => {
);
};

export const createDomainLazyRoute = createLazyRoute('/domains/create')({
component: CreateDomain,
});

const StyledGrid = styled(Grid, { label: 'StyledGrid' })({
width: '100%',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import type { Domain } from '@linode/api-v4';

interface DisableDomainDialogProps {
domain: Domain | undefined;
isFetching: boolean;
onClose: () => void;
open: boolean;
}

export const DisableDomainDialog = React.memo(
(props: DisableDomainDialogProps) => {
const { domain, onClose, open } = props;
const { domain, isFetching, onClose, open } = props;
const {
error,
isPending,
Expand Down Expand Up @@ -56,6 +57,7 @@ export const DisableDomainDialog = React.memo(
/>
}
error={error?.[0]?.reason}
isFetching={isFetching}
onClose={onClose}
open={open}
title={`Disable Domain ${domain?.domain}?`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { userEvent } from '@testing-library/user-event';
import * as React from 'react';

import { domainFactory } from 'src/factories/domain';
import { renderWithTheme } from 'src/utilities/testHelpers';
import { renderWithThemeAndRouter } from 'src/utilities/testHelpers';

import { DomainActionMenu } from './DomainActionMenu';

Expand All @@ -15,7 +15,7 @@ const props = {

describe('Domain action menu', () => {
it('should include basic Domain actions', async () => {
const { getByText, queryByLabelText } = renderWithTheme(
const { getByText, queryByLabelText } = await renderWithThemeAndRouter(
<DomainActionMenu domain={domainFactory.build()} {...props} />
);

Expand Down
26 changes: 15 additions & 11 deletions packages/manager/src/features/Domains/DomainDetail/DomainDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { CircleProgress, Notice, Paper, Typography } from '@linode/ui';
import { styled } from '@mui/material/styles';
import Grid from '@mui/material/Unstable_Grid2';
import { useLocation, useNavigate, useParams } from '@tanstack/react-router';
import * as React from 'react';
import { useHistory, useLocation, useParams } from 'react-router-dom';

import { ErrorState } from 'src/components/ErrorState/ErrorState';
import { LandingHeader } from 'src/components/LandingHeader';
Expand All @@ -18,14 +18,18 @@ import { DeleteDomain } from '../DeleteDomain';
import DomainRecords from '../DomainRecords';
import { DownloadDNSZoneFileButton } from '../DownloadDNSZoneFileButton';

export const DomainDetail = () => {
const params = useParams<{ domainId: string }>();
const domainId = Number(params.domainId);

const history = useHistory();
const location = useLocation<{ recordError?: string }>();
import type { DomainState } from 'src/routes/domains';

const { data: domain, error, isLoading } = useDomainQuery(domainId);
export const DomainDetail = () => {
const navigate = useNavigate();
const params = useParams({ from: '/domains/$domainId' });
const domainId = params.domainId;
const location = useLocation();
const locationState = location.state as DomainState;
const { data: domain, error, isLoading } = useDomainQuery(
domainId,
!!domainId
);
const { mutateAsync: updateDomain } = useUpdateDomainMutation();
const {
data: records,
Expand Down Expand Up @@ -110,8 +114,8 @@ export const DomainDetail = () => {
docsLink="https://techdocs.akamai.com/cloud-computing/docs/dns-manager"
title="Domain Details"
/>
{location.state && location.state.recordError && (
<StyledNotice text={location.state.recordError} variant="error" />
{locationState?.recordError && (
<StyledNotice text={locationState.recordError} variant="error" />
)}
<StyledRootGrid container>
<StyledMainGrid xs={12}>
Expand All @@ -138,7 +142,7 @@ export const DomainDetail = () => {
<DeleteDomain
domainId={domain.id}
domainLabel={domain.domain}
onSuccess={() => history.push('/domains')}
onSuccess={() => navigate({ to: '/domains' })}
/>
</StyledDiv>
</StyledTagSectionGrid>
Expand Down
11 changes: 3 additions & 8 deletions packages/manager/src/features/Domains/DomainDetail/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { CircleProgress } from '@linode/ui';
import { createLazyRoute } from '@tanstack/react-router';
import { useParams } from '@tanstack/react-router';
import * as React from 'react';
import { useParams } from 'react-router-dom';

import { ErrorState } from 'src/components/ErrorState/ErrorState';
import { NotFound } from 'src/components/NotFound';
Expand All @@ -17,8 +16,8 @@ const DomainDetail = React.lazy(() =>
);

export const DomainDetailRouting = () => {
const params = useParams<{ domainId: string }>();
const domainId = Number(params.domainId);
const params = useParams({ from: '/domains/$domainId' });
const domainId = params.domainId;

const { data: domain, error, isLoading } = useDomainQuery(domainId);

Expand All @@ -43,7 +42,3 @@ export const DomainDetailRouting = () => {
// page with an open drawer.
return <DomainsLanding domainForEditing={domain} />;
};

export const domainDetailLazyRoute = createLazyRoute('/domains/$domainId')({
component: DomainDetailRouting,
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Notice, TextField } from '@linode/ui';
import { useFormik } from 'formik';
import * as React from 'react';
import { useHistory } from 'react-router-dom';
import { useNavigate } from '@tanstack/react-router';

import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { Drawer } from 'src/components/Drawer';
Expand All @@ -21,7 +21,7 @@ export const DomainZoneImportDrawer = (props: DomainZoneImportDrawerProps) => {
const { data: profile } = useProfile();
const { data: grants } = useGrants();

const history = useHistory();
const navigate = useNavigate();

const { error, mutateAsync: importZone, reset } = useImportZoneMutation();

Expand All @@ -32,7 +32,10 @@ export const DomainZoneImportDrawer = (props: DomainZoneImportDrawerProps) => {
},
onSubmit: async (values) => {
const result = await importZone(values);
history.push(`/domains/${result.id}`);
navigate({
params: { domainId: result.id },
to: '/domains/$domainId',
});
},
});

Expand Down
22 changes: 19 additions & 3 deletions packages/manager/src/features/Domains/DomainsLanding.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import * as React from 'react';

import { renderWithTheme } from 'src/utilities/testHelpers';
import { migrationRouteTree } from 'src/routes';
import { renderWithThemeAndRouter } from 'src/utilities/testHelpers';

import { DomainsLanding } from './DomainsLanding';

vi.mock('src/queries/domains', async () => {
const actual = await vi.importActual('src/queries/domains');
return {
...actual,
useDomainsQuery: vi.fn().mockReturnValue({
data: undefined,
error: null,
isLoading: true,
}),
};
});

describe('Domains Landing', () => {
it('should initially render a loading state', () => {
const { getByTestId } = renderWithTheme(<DomainsLanding />);
it('should initially render a loading state', async () => {
const { getByTestId } = await renderWithThemeAndRouter(<DomainsLanding />, {
initialRoute: '/domains',
routeTree: migrationRouteTree,
});
expect(getByTestId('circle-progress')).toBeInTheDocument();
});
});
Loading