{linkGroup.title}
+ > + } + isActiveProductFamily={ + activeProductFamily === linkGroup.title + } + expanded={!collapsedAccordions.includes(idx)} + isCollapsed={isCollapsed} + onChange={() => accordionClicked(idx)} + > + {PrimaryLinks} +
diff --git a/packages/manager/src/features/Managed/ManagedDashboardCard/ManagedChartPanel.tsx b/packages/manager/src/features/Managed/ManagedDashboardCard/ManagedChartPanel.tsx
index 83bb9e33cba..d8290803e1c 100644
--- a/packages/manager/src/features/Managed/ManagedDashboardCard/ManagedChartPanel.tsx
+++ b/packages/manager/src/features/Managed/ManagedDashboardCard/ManagedChartPanel.tsx
@@ -1,10 +1,8 @@
-import { DataSeries, ManagedStatsData } from '@linode/api-v4/lib/managed';
+import { Box } from '@linode/ui';
import { useTheme } from '@mui/material/styles';
-import { Theme } from '@mui/material/styles';
import * as React from 'react';
import { AreaChart } from 'src/components/AreaChart/AreaChart';
-import { Box } from 'src/components/Box';
import { CircleProgress } from 'src/components/CircleProgress';
import { ErrorState } from 'src/components/ErrorState/ErrorState';
import { TabbedPanel } from 'src/components/TabbedPanel/TabbedPanel';
@@ -23,6 +21,9 @@ import {
StyledRootDiv,
} from './ManagedChartPanel.styles';
+import type { DataSeries, ManagedStatsData } from '@linode/api-v4/lib/managed';
+import type { Theme } from '@mui/material/styles';
+
const chartHeight = 300;
interface NetworkTransferProps {
@@ -78,7 +79,7 @@ const createTabs = (
return (
{summaryCopy}
-
+
{summaryCopy}
-
+
{summaryCopy}
-
+
{
);
return (
-
+
{
legendTitle: 'Connections',
},
]}
+ margin={{
+ bottom: 0,
+ left: -15,
+ right: 0,
+ top: 0,
+ }}
xAxis={{
tickFormat: 'hh a',
tickGap: 60,
@@ -176,7 +182,7 @@ export const TablesPanel = () => {
}
return (
-
+
{
legendTitle: 'Traffic Out',
},
]}
+ margin={{
+ bottom: 0,
+ left: -15,
+ right: 0,
+ top: 0,
+ }}
xAxis={{
tickFormat: 'hh a',
tickGap: 60,
@@ -255,11 +267,9 @@ const StyledTitle = styled(Typography, {
export const StyledBottomLegend = styled('div', {
label: 'StyledBottomLegend',
-})(({ theme }) => ({
- backgroundColor: theme.bg.offWhite,
+})(() => ({
color: '#777',
fontSize: 14,
- margin: `${theme.spacing(2)} ${theme.spacing(1)} ${theme.spacing(1)}`,
}));
const StyledPanel = styled(Paper, {
diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancerPassiveCheck.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancerPassiveCheck.tsx
index 7275e5f149b..cd54cac1d4d 100644
--- a/packages/manager/src/features/NodeBalancers/NodeBalancerPassiveCheck.tsx
+++ b/packages/manager/src/features/NodeBalancers/NodeBalancerPassiveCheck.tsx
@@ -1,8 +1,8 @@
+import { FormHelperText } from '@linode/ui';
import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';
import { FormControlLabel } from 'src/components/FormControlLabel';
-import { FormHelperText } from 'src/components/FormHelperText';
import { Toggle } from 'src/components/Toggle/Toggle';
import { Typography } from 'src/components/Typography';
diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLandingEmptyState.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLandingEmptyState.tsx
index de40d0a7b84..ac34dd975cd 100644
--- a/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLandingEmptyState.tsx
+++ b/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLandingEmptyState.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { useHistory } from 'react-router-dom';
-import NodeBalancer from 'src/assets/icons/entityIcons/nodebalancer.svg';
+import NodeBalancerIcon from 'src/assets/icons/entityIcons/nodebalancer.svg';
import { DocumentTitleSegment } from 'src/components/DocumentTitle';
import { ResourcesSection } from 'src/components/EmptyLandingPageResources/ResourcesSection';
import { getRestrictedResourceText } from 'src/features/Account/utils';
@@ -47,7 +47,7 @@ export const NodeBalancerLandingEmptyState = () => {
]}
gettingStartedGuidesData={gettingStartedGuides}
headers={headers}
- icon={NodeBalancer}
+ icon={NodeBalancerIcon}
linkAnalyticsEvent={linkAnalyticsEvent}
youtubeLinkData={youtubeLinkData}
/>
diff --git a/packages/manager/src/features/NotificationCenter/Events/NotificationCenterEvent.tsx b/packages/manager/src/features/NotificationCenter/Events/NotificationCenterEvent.tsx
index 9940a8527d7..ee2da2ca32c 100644
--- a/packages/manager/src/features/NotificationCenter/Events/NotificationCenterEvent.tsx
+++ b/packages/manager/src/features/NotificationCenter/Events/NotificationCenterEvent.tsx
@@ -1,8 +1,8 @@
+import { Box } from '@linode/ui';
import { useTheme } from '@mui/material';
import * as React from 'react';
import { BarPercent } from 'src/components/BarPercent';
-import { Box } from 'src/components/Box';
import { Typography } from 'src/components/Typography';
import {
formatProgressEvent,
diff --git a/packages/manager/src/features/NotificationCenter/NotificationCenter.styles.ts b/packages/manager/src/features/NotificationCenter/NotificationCenter.styles.ts
index 6820ce92683..e8e24d268f6 100644
--- a/packages/manager/src/features/NotificationCenter/NotificationCenter.styles.ts
+++ b/packages/manager/src/features/NotificationCenter/NotificationCenter.styles.ts
@@ -1,12 +1,11 @@
+import { Box, omittedProps } from '@linode/ui';
import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
import { styled } from '@mui/material';
import { makeStyles } from 'tss-react/mui';
import { Avatar } from 'src/components/Avatar/Avatar';
-import { Box } from 'src/components/Box';
import { Link } from 'src/components/Link';
import { Typography } from 'src/components/Typography';
-import { omittedProps } from 'src/utilities/omittedProps';
import type { NotificationCenterNotificationMessageProps } from './types';
import type { Theme } from '@mui/material/styles';
diff --git a/packages/manager/src/features/NotificationCenter/Notifications/NotificationCenterNotificationMessage.tsx b/packages/manager/src/features/NotificationCenter/Notifications/NotificationCenterNotificationMessage.tsx
index 30b9b3e01f2..dedfb32743c 100644
--- a/packages/manager/src/features/NotificationCenter/Notifications/NotificationCenterNotificationMessage.tsx
+++ b/packages/manager/src/features/NotificationCenter/Notifications/NotificationCenterNotificationMessage.tsx
@@ -1,9 +1,9 @@
+import { Box } from '@linode/ui';
import ErrorIcon from '@mui/icons-material/Error';
import WarningIcon from '@mui/icons-material/Warning';
import { useTheme } from '@mui/material/styles';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { Divider } from 'src/components/Divider';
import { Typography } from 'src/components/Typography';
import { sanitizeHTML } from 'src/utilities/sanitizeHTML';
diff --git a/packages/manager/src/features/NotificationCenter/Notifications/NotificationCenterNotificationsContainer.tsx b/packages/manager/src/features/NotificationCenter/Notifications/NotificationCenterNotificationsContainer.tsx
index bc11526630e..4a2f4e03f56 100644
--- a/packages/manager/src/features/NotificationCenter/Notifications/NotificationCenterNotificationsContainer.tsx
+++ b/packages/manager/src/features/NotificationCenter/Notifications/NotificationCenterNotificationsContainer.tsx
@@ -1,7 +1,7 @@
+import { Box } from '@linode/ui';
import * as React from 'react';
import { Accordion } from 'src/components/Accordion';
-import { Box } from 'src/components/Box';
import { Hidden } from 'src/components/Hidden';
import { Link } from 'src/components/Link';
import { Typography } from 'src/components/Typography';
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessCell.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessCell.tsx
index 265e90ec4ea..00a7b857da7 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessCell.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessCell.tsx
@@ -1,10 +1,10 @@
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
+import { Tooltip } from '@linode/ui';
import { styled } from '@mui/material/styles';
import * as React from 'react';
import Check from 'src/assets/icons/monitor-ok.svg';
import { Radio } from 'src/components/Radio/Radio';
-import { Tooltip } from 'src/components/Tooltip';
interface RadioButton extends HTMLInputElement {
name: string;
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx
index 3927970ff54..9e067f8e7fe 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx
@@ -27,7 +27,7 @@ import { AccessKeyDrawer } from './AccessKeyDrawer';
import { AccessKeyTable } from './AccessKeyTable/AccessKeyTable';
import { OMC_AccessKeyDrawer } from './OMC_AccessKeyDrawer';
import { RevokeAccessKeyDialog } from './RevokeAccessKeyDialog';
-import ViewPermissionsDrawer from './ViewPermissionsDrawer';
+import { ViewPermissionsDrawer } from './ViewPermissionsDrawer';
import type { MODE, OpenAccessDrawer } from './types';
import type {
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyRegions/SelectedRegionsList.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyRegions/SelectedRegionsList.tsx
index 571139055ea..401f2f9aed0 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyRegions/SelectedRegionsList.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyRegions/SelectedRegionsList.tsx
@@ -1,6 +1,6 @@
+import { Box } from '@linode/ui';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { Flag } from 'src/components/Flag';
import { RemovableSelectionsList } from 'src/components/RemovableSelectionsList/RemovableSelectionsList';
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyActionMenu.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyActionMenu.tsx
index 5b08be32e43..b0c13b737c1 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyActionMenu.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyActionMenu.tsx
@@ -1,29 +1,34 @@
-import { ObjectStorageKey } from '@linode/api-v4/lib/object-storage';
-import { styled } from '@mui/material/styles';
+import { useMediaQuery } from '@mui/material';
import * as React from 'react';
import { ActionMenu } from 'src/components/ActionMenu/ActionMenu';
-import { Hidden } from 'src/components/Hidden';
import { InlineMenuAction } from 'src/components/InlineMenuAction/InlineMenuAction';
+import { Stack } from 'src/components/Stack';
import { useAccountManagement } from 'src/hooks/useAccountManagement';
import { useFlags } from 'src/hooks/useFlags';
import { isFeatureEnabledV2 } from 'src/utilities/accountCapabilities';
-import { OpenAccessDrawer } from '../types';
+import type { OpenAccessDrawer } from '../types';
+import type { ObjectStorageKey } from '@linode/api-v4';
+import type { Theme } from '@mui/material';
interface Props {
label: string;
objectStorageKey: ObjectStorageKey;
openDrawer: OpenAccessDrawer;
+ openHostnamesDrawer: () => void;
openRevokeDialog: (key: ObjectStorageKey) => void;
}
-export const AccessKeyActionMenu = ({
- label,
- objectStorageKey,
- openDrawer,
- openRevokeDialog,
-}: Props) => {
+export const AccessKeyActionMenu = (props: Props) => {
+ const {
+ label,
+ objectStorageKey,
+ openDrawer,
+ openHostnamesDrawer,
+ openRevokeDialog,
+ } = props;
+
const flags = useFlags();
const { account } = useAccountManagement();
@@ -33,6 +38,10 @@ export const AccessKeyActionMenu = ({
account?.capabilities ?? []
);
+ const isSmallViewport = useMediaQuery((theme) =>
+ theme.breakpoints.down('md')
+ );
+
const actions = [
{
onClick: () => {
@@ -46,6 +55,14 @@ export const AccessKeyActionMenu = ({
},
title: 'Permissions',
},
+ ...(isObjMultiClusterEnabled
+ ? [
+ {
+ onClick: openHostnamesDrawer,
+ title: 'View Regions/S3 Hostnames',
+ },
+ ]
+ : []),
{
onClick: () => {
openRevokeDialog(objectStorageKey);
@@ -54,40 +71,24 @@ export const AccessKeyActionMenu = ({
},
];
+ if (isObjMultiClusterEnabled || isSmallViewport) {
+ return (
+
+ );
+ }
+
return (
-
- {isObjMultiClusterEnabled ? (
-
+ {actions.map((action) => (
+
- ) : (
- <>
-
- {actions.map((thisAction) => (
-
- ))}
-
-
-
-
- >
- )}
-
+ ))}
+
);
};
-
-const StyledInlineActionsContainer = styled('div', {
- label: 'StyledInlineActionsContainer',
-})(() => ({
- alignItems: 'center',
- display: 'flex',
- justifyContent: 'flex-end',
-}));
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.styles.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.styles.tsx
deleted file mode 100644
index 467b2c38a0d..00000000000
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.styles.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { styled } from '@mui/material';
-
-import { TableCell } from 'src/components/TableCell';
-import { omittedProps } from 'src/utilities/omittedProps';
-
-import type { TableCellProps } from 'src/components/TableCell';
-
-interface StyledLastColumnCellProps extends TableCellProps {
- addPaddingRight?: boolean;
-}
-export const StyledLastColumnCell = styled(TableCell, {
- shouldForwardProp: omittedProps(['addPaddingRight']),
-})(({ addPaddingRight }) => ({
- '&&:last-child': {
- 'padding-right': addPaddingRight ? '15px' : 0,
- },
-}));
-
-export const StyledLabelCell = styled(TableCell)(() => ({
- width: '35%',
-}));
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.test.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.test.tsx
index 882506165a4..d0f68f2e7e8 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.test.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.test.tsx
@@ -2,7 +2,9 @@ import * as React from 'react';
import { renderWithTheme } from 'src/utilities/testHelpers';
-import { AccessKeyTable, AccessKeyTableProps } from './AccessKeyTable';
+import { AccessKeyTable } from './AccessKeyTable';
+
+import type { AccessKeyTableProps } from './AccessKeyTable';
describe('ObjectStorageKeyTable', () => {
const props: AccessKeyTableProps = {
@@ -34,9 +36,9 @@ describe('ObjectStorageKeyTable', () => {
getByText('We were unable to load your Access Keys.');
});
- it('returns an empty state if there is no data', () => {
+ it('returns an empty state if there are no access keys', () => {
const { getByText } = renderWithTheme(
-
+
);
getByText('No items to display.');
});
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.tsx
index d39ecf5cb58..6e9cded14ee 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.tsx
@@ -1,7 +1,9 @@
import React, { useState } from 'react';
+import { Hidden } from 'src/components/Hidden';
import { Table } from 'src/components/Table';
import { TableBody } from 'src/components/TableBody';
+import { TableCell } from 'src/components/TableCell';
import { TableHead } from 'src/components/TableHead';
import { TableRow } from 'src/components/TableRow';
import { useAccountManagement } from 'src/hooks/useAccountManagement';
@@ -9,15 +11,14 @@ import { useFlags } from 'src/hooks/useFlags';
import { isFeatureEnabledV2 } from 'src/utilities/accountCapabilities';
import { HostNamesDrawer } from '../HostNamesDrawer';
-import { StyledLabelCell, StyledLastColumnCell } from './AccessKeyTable.styles';
import { AccessKeyTableBody } from './AccessKeyTableBody';
import type { OpenAccessDrawer } from '../types';
import type {
+ APIError,
ObjectStorageKey,
ObjectStorageKeyRegions,
-} from '@linode/api-v4/lib/object-storage';
-import type { APIError } from '@linode/api-v4/lib/types';
+} from '@linode/api-v4';
export interface AccessKeyTableProps {
data: ObjectStorageKey[] | undefined;
@@ -56,25 +57,27 @@ export const AccessKeyTable = (props: AccessKeyTableProps) => {
<>
-
- Label
- Access Key
+
+ ({
+ [theme.breakpoints.up('md')]: {
+ minWidth: 120,
+ },
+ })}
+ >
+ Label
+
+ Access Key
{isObjMultiClusterEnabled && (
-
- Regions/S3 Hostnames
-
+
+ Regions/S3 Hostnames
+
)}
-
- Actions
-
+
@@ -82,6 +85,7 @@ export const AccessKeyTable = (props: AccessKeyTableProps) => {
data={data}
error={error}
isLoading={isLoading}
+ isObjMultiClusterEnabled={isObjMultiClusterEnabled}
isRestrictedUser={isRestrictedUser}
openDrawer={openDrawer}
openRevokeDialog={openRevokeDialog}
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableBody.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableBody.tsx
index 3e193b305f6..34c0c9493a2 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableBody.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableBody.tsx
@@ -1,73 +1,79 @@
-import {
- ObjectStorageKey,
- ObjectStorageKeyRegions,
-} from '@linode/api-v4/lib/object-storage';
-import { APIError } from '@linode/api-v4/lib/types';
import React from 'react';
import { TableRowEmpty } from 'src/components/TableRowEmpty/TableRowEmpty';
import { TableRowError } from 'src/components/TableRowError/TableRowError';
import { TableRowLoading } from 'src/components/TableRowLoading/TableRowLoading';
-import { OpenAccessDrawer } from '../types';
import { AccessKeyTableRow } from './AccessKeyTableRow';
-type Props = {
+import type { OpenAccessDrawer } from '../types';
+import type {
+ APIError,
+ ObjectStorageKey,
+ ObjectStorageKeyRegions,
+} from '@linode/api-v4';
+
+interface Props {
data: ObjectStorageKey[] | undefined;
error: APIError[] | null | undefined;
isLoading: boolean;
+ isObjMultiClusterEnabled: boolean;
isRestrictedUser: boolean;
openDrawer: OpenAccessDrawer;
openRevokeDialog: (objectStorageKey: ObjectStorageKey) => void;
setHostNames: (hostNames: ObjectStorageKeyRegions[]) => void;
setShowHostNamesDrawers: (show: boolean) => void;
-};
+}
+
+export const AccessKeyTableBody = (props: Props) => {
+ const {
+ data,
+ error,
+ isLoading,
+ isObjMultiClusterEnabled,
+ isRestrictedUser,
+ openDrawer,
+ openRevokeDialog,
+ setHostNames,
+ setShowHostNamesDrawers,
+ } = props;
+
+ const cols = isObjMultiClusterEnabled ? 4 : 3;
-export const AccessKeyTableBody = ({
- data,
- error,
- isLoading,
- isRestrictedUser,
- openDrawer,
- openRevokeDialog,
- setHostNames,
- setShowHostNamesDrawers,
-}: Props) => {
if (isRestrictedUser) {
- return ;
+ return ;
}
if (isLoading) {
- return ;
+ return (
+
+ );
}
if (error) {
return (
);
}
- return (
- <>
- {data && data.length > 0 ? (
- <>
- {data.map((eachKey: ObjectStorageKey, index) => (
-
- ))}
- >
- ) : (
-
- )}
- >
- );
+ if (data?.length === 0) {
+ return ;
+ }
+
+ return data?.map((key) => (
+
+ ));
};
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableRow.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableRow.tsx
index 76eb64490bb..f9755977e65 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableRow.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableRow.tsx
@@ -2,6 +2,9 @@ import { styled } from '@mui/material/styles';
import React from 'react';
import { CopyTooltip } from 'src/components/CopyTooltip/CopyTooltip';
+import { Hidden } from 'src/components/Hidden';
+import { MaskableText } from 'src/components/MaskableText/MaskableText';
+import { Stack } from 'src/components/Stack';
import { TableCell } from 'src/components/TableCell';
import { TableRow } from 'src/components/TableRow';
import { Typography } from 'src/components/Typography';
@@ -10,30 +13,28 @@ import { useFlags } from 'src/hooks/useFlags';
import { isFeatureEnabledV2 } from 'src/utilities/accountCapabilities';
import { AccessKeyActionMenu } from './AccessKeyActionMenu';
-import { StyledLastColumnCell } from './AccessKeyTable.styles';
import { HostNameTableCell } from './HostNameTableCell';
import type { OpenAccessDrawer } from '../types';
-import type {
- ObjectStorageKey,
- ObjectStorageKeyRegions,
-} from '@linode/api-v4/lib/object-storage';
+import type { ObjectStorageKey, ObjectStorageKeyRegions } from '@linode/api-v4';
-type Props = {
+interface Props {
openDrawer: OpenAccessDrawer;
openRevokeDialog: (storageKeyData: ObjectStorageKey) => void;
setHostNames: (hostNames: ObjectStorageKeyRegions[]) => void;
setShowHostNamesDrawers: (show: boolean) => void;
storageKeyData: ObjectStorageKey;
-};
+}
+
+export const AccessKeyTableRow = (props: Props) => {
+ const {
+ openDrawer,
+ openRevokeDialog,
+ setHostNames,
+ setShowHostNamesDrawers,
+ storageKeyData,
+ } = props;
-export const AccessKeyTableRow = ({
- openDrawer,
- openRevokeDialog,
- setHostNames,
- setShowHostNamesDrawers,
- storageKeyData,
-}: Props) => {
const { account } = useAccountManagement();
const flags = useFlags();
@@ -45,33 +46,36 @@ export const AccessKeyTableRow = ({
return (
-
-
- {storageKeyData.label}
-
-
+ {storageKeyData.label}
-
- {storageKeyData.access_key}
+
+
+ {storageKeyData.access_key}
+
-
+
{isObjMultiClusterEnabled && (
-
+
+
+
)}
-
-
+
{
+ setShowHostNamesDrawers(true);
+ setHostNames(storageKeyData.regions);
+ }}
label={storageKeyData.label}
objectStorageKey={storageKeyData}
openDrawer={openDrawer}
openRevokeDialog={openRevokeDialog}
/>
-
+
);
};
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/HostNameTableCell.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/HostNameTableCell.tsx
index 14f0595acca..bb67e6c83f8 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/HostNameTableCell.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/HostNameTableCell.tsx
@@ -7,22 +7,17 @@ import { TableCell } from 'src/components/TableCell';
import { useRegionsQuery } from 'src/queries/regions/regions';
import { getRegionsByRegionId } from 'src/utilities/regions';
-import type {
- ObjectStorageKey,
- ObjectStorageKeyRegions,
-} from '@linode/api-v4/lib/object-storage';
+import type { ObjectStorageKey, ObjectStorageKeyRegions } from '@linode/api-v4';
-type Props = {
+interface Props {
setHostNames: (hostNames: ObjectStorageKeyRegions[]) => void;
setShowHostNamesDrawers: (show: boolean) => void;
storageKeyData: ObjectStorageKey;
-};
+}
+
+export const HostNameTableCell = (props: Props) => {
+ const { setHostNames, setShowHostNamesDrawers, storageKeyData } = props;
-export const HostNameTableCell = ({
- setHostNames,
- setShowHostNamesDrawers,
- storageKeyData,
-}: Props) => {
const { data: regionsData } = useRegionsQuery();
const regionsLookup = regionsData && getRegionsByRegionId(regionsData);
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/CopyAllHostnames.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/CopyAllHostnames.tsx
index 10353fb94c3..b1a42407491 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/CopyAllHostnames.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/CopyAllHostnames.tsx
@@ -1,11 +1,9 @@
+import { Box, InputLabel, Tooltip } from '@linode/ui';
import { styled } from '@mui/material/styles';
import copy from 'copy-to-clipboard';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { StyledLinkButton } from 'src/components/Button/StyledLinkButton';
-import { InputLabel } from 'src/components/InputLabel';
-import { Tooltip } from 'src/components/Tooltip';
export interface Props {
hideShowAll?: boolean;
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesDrawer.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesDrawer.tsx
index 96944066b59..c7d2147f71b 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesDrawer.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesDrawer.tsx
@@ -1,6 +1,6 @@
+import { Box } from '@linode/ui';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { CopyableTextField } from 'src/components/CopyableTextField/CopyableTextField';
import { Drawer } from 'src/components/Drawer';
import { useRegionsQuery } from 'src/queries/regions/regions';
@@ -56,13 +56,17 @@ export const HostNamesDrawer = (props: Props) => {
return (
);
})}
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesList.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesList.tsx
index da1e0406a3f..7229f67a0fd 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesList.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesList.tsx
@@ -1,11 +1,10 @@
+import { Box, omittedProps } from '@linode/ui';
import { styled } from '@mui/material/styles';
import React, { useRef } from 'react';
-import { Box } from 'src/components/Box';
import { CopyableTextField } from 'src/components/CopyableTextField/CopyableTextField';
import { List } from 'src/components/List';
import { useRegionsQuery } from 'src/queries/regions/regions';
-import { omittedProps } from 'src/utilities/omittedProps';
import { getRegionsByRegionId } from 'src/utilities/regions';
import type { ObjectStorageKey } from '@linode/api-v4/lib/object-storage';
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/ViewPermissionsDrawer.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/ViewPermissionsDrawer.tsx
index 73ebb5ec950..f7053031ad7 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/ViewPermissionsDrawer.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/ViewPermissionsDrawer.tsx
@@ -1,4 +1,3 @@
-import { ObjectStorageKey } from '@linode/api-v4/lib/object-storage';
import * as React from 'react';
import { Drawer } from 'src/components/Drawer';
@@ -10,15 +9,15 @@ import { isFeatureEnabledV2 } from 'src/utilities/accountCapabilities';
import { AccessTable } from './AccessTable';
import { BucketPermissionsTable } from './BucketPermissionsTable';
+import type { ObjectStorageKey } from '@linode/api-v4';
+
export interface Props {
objectStorageKey: ObjectStorageKey | null;
onClose: () => void;
open: boolean;
}
-type CombinedProps = Props;
-
-export const ViewPermissionsDrawer: React.FC = (props) => {
+export const ViewPermissionsDrawer = (props: Props) => {
const { objectStorageKey, onClose, open } = props;
const flags = useFlags();
@@ -30,18 +29,14 @@ export const ViewPermissionsDrawer: React.FC = (props) => {
account?.capabilities ?? []
);
- if (objectStorageKey === null) {
- return null;
- }
-
return (
- {objectStorageKey.bucket_access === null ? (
+ {!objectStorageKey ? null : objectStorageKey.bucket_access === null ? (
This key has unlimited access to all buckets on your account.
@@ -70,5 +65,3 @@ export const ViewPermissionsDrawer: React.FC = (props) => {
);
};
-
-export default React.memo(ViewPermissionsDrawer);
diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketAccess.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketAccess.tsx
index 096209ce702..cb499aa8c23 100644
--- a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketAccess.tsx
+++ b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketAccess.tsx
@@ -1,7 +1,7 @@
+import { Paper } from '@linode/ui';
import { styled } from '@mui/material/styles';
import * as React from 'react';
-import { Paper } from 'src/components/Paper';
import { Typography } from 'src/components/Typography';
import { AccessSelect } from './AccessSelect';
diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketDetail.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketDetail.tsx
index ec2f91870a0..94dd1071e05 100644
--- a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketDetail.tsx
+++ b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketDetail.tsx
@@ -1,5 +1,6 @@
import { getObjectList, getObjectURL } from '@linode/api-v4/lib/object-storage';
-import { InfiniteData, useQueryClient } from '@tanstack/react-query';
+import { Box } from '@linode/ui';
+import { useQueryClient } from '@tanstack/react-query';
import produce from 'immer';
import { useSnackbar } from 'notistack';
import * as React from 'react';
@@ -8,7 +9,6 @@ import { Waypoint } from 'react-waypoint';
import { debounce } from 'throttle-debounce';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
-import { Box } from 'src/components/Box';
import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog';
import { Hidden } from 'src/components/Hidden';
import { Table } from 'src/components/Table';
@@ -58,6 +58,7 @@ import type {
ObjectStorageObject,
ObjectStorageObjectList,
} from '@linode/api-v4';
+import type { InfiniteData } from '@tanstack/react-query';
interface MatchParams {
bucketName: string;
diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketProperties.styles.ts b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketProperties.styles.ts
index 96183503462..32af30ee1d0 100644
--- a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketProperties.styles.ts
+++ b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketProperties.styles.ts
@@ -1,7 +1,7 @@
+import { Paper } from '@linode/ui';
import { styled } from '@mui/material/styles';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
-import { Paper } from 'src/components/Paper';
import { Typography } from 'src/components/Typography';
export const StyledText = styled(Typography, {
diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketSSL.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketSSL.tsx
index e3ed6211e24..c410769f1db 100644
--- a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketSSL.tsx
+++ b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketSSL.tsx
@@ -1,3 +1,4 @@
+import { Paper } from '@linode/ui';
import { useTheme } from '@mui/material/styles';
import Grid from '@mui/material/Unstable_Grid2';
import { useFormik } from 'formik';
@@ -11,7 +12,6 @@ import { ConfirmationDialog } from 'src/components/ConfirmationDialog/Confirmati
import { ErrorState } from 'src/components/ErrorState/ErrorState';
import { Link } from 'src/components/Link';
import { Notice } from 'src/components/Notice/Notice';
-import { Paper } from 'src/components/Paper';
import { TextField } from 'src/components/TextField';
import { Typography } from 'src/components/Typography';
import {
diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectTableRow.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectTableRow.tsx
index 96d32373fd1..7cbcf7d4989 100644
--- a/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectTableRow.tsx
+++ b/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectTableRow.tsx
@@ -1,8 +1,8 @@
-import Grid from '@mui/material/Unstable_Grid2';
+import { Box } from '@linode/ui';
import { styled } from '@mui/material/styles';
+import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { StyledLinkButton } from 'src/components/Button/StyledLinkButton';
import { DateTimeDisplay } from 'src/components/DateTimeDisplay';
import { EntityIcon } from 'src/components/EntityIcon/EntityIcon';
diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketDetailsDrawer.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketDetailsDrawer.tsx
index 7743e840963..9a8c8f2dc55 100644
--- a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketDetailsDrawer.tsx
+++ b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketDetailsDrawer.tsx
@@ -5,6 +5,7 @@ import { CopyTooltip } from 'src/components/CopyTooltip/CopyTooltip';
import { Divider } from 'src/components/Divider';
import { Drawer } from 'src/components/Drawer';
import { Link } from 'src/components/Link';
+import { MaskableText } from 'src/components/MaskableText/MaskableText';
import { Typography } from 'src/components/Typography';
import { useAccountManagement } from 'src/hooks/useAccountManagement';
import { useFlags } from 'src/hooks/useFlags';
@@ -108,12 +109,14 @@ export const BucketDetailsDrawer = React.memo(
) : null}
{hostname && (
-
-
- {truncateMiddle(hostname, 50)}
-
-
-
+
+
+
+ {truncateMiddle(hostname, 50)}
+
+
+
+
)}
{(formattedCreated || cluster) && (
diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketRateLimitTable.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketRateLimitTable.tsx
index aa2a9571982..b7ab4530a89 100644
--- a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketRateLimitTable.tsx
+++ b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketRateLimitTable.tsx
@@ -1,6 +1,6 @@
+import { Box } from '@linode/ui';
import React from 'react';
-import { Box } from 'src/components/Box';
import { FormControlLabel } from 'src/components/FormControlLabel';
import { FormLabel } from 'src/components/FormLabel';
import { Link } from 'src/components/Link';
diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketTableRow.styles.ts b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketTableRow.styles.ts
index 22dfa151e8e..38006ef18dc 100644
--- a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketTableRow.styles.ts
+++ b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketTableRow.styles.ts
@@ -1,31 +1,6 @@
import { styled } from '@mui/material/styles';
-import { Link } from 'react-router-dom';
import { TableCell } from 'src/components/TableCell';
-import { TableRow } from 'src/components/TableRow';
-
-export const StyledBucketRow = styled(TableRow, {
- label: 'StyledBucketRow',
-})(({ theme }) => ({
- backgroundColor: theme.bg.white,
-}));
-
-export const StyledBucketNameWrapper = styled('div', {
- label: 'StyledBucketNameWrapper',
-})(() => ({
- alignItems: 'center',
- display: 'flex',
- flexFlow: 'row nowrap',
- wordBreak: 'break-all',
-}));
-
-export const StyledBucketLabelLink = styled(Link, {
- label: 'StyledBucketLabelLink',
-})(() => ({
- '&:hover, &:focus': {
- textDecoration: 'underline',
- },
-}));
export const StyledBucketRegionCell = styled(TableCell, {
label: 'StyledBucketRegionCell',
diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketTableRow.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketTableRow.tsx
index 3f9aee4ab2d..a84a923baba 100644
--- a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketTableRow.tsx
+++ b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketTableRow.tsx
@@ -1,9 +1,12 @@
-import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';
import { DateTimeDisplay } from 'src/components/DateTimeDisplay';
import { Hidden } from 'src/components/Hidden';
+import { Link } from 'src/components/Link';
+import { MaskableText } from 'src/components/MaskableText/MaskableText';
+import { Stack } from 'src/components/Stack';
import { TableCell } from 'src/components/TableCell';
+import { TableRow } from 'src/components/TableRow';
import { Typography } from 'src/components/Typography';
import { useAccountManagement } from 'src/hooks/useAccountManagement';
import { useFlags } from 'src/hooks/useFlags';
@@ -15,11 +18,8 @@ import { readableBytes } from 'src/utilities/unitConversions';
import { BucketActionMenu } from './BucketActionMenu';
import {
- StyledBucketLabelLink,
- StyledBucketNameWrapper,
StyledBucketObjectsCell,
StyledBucketRegionCell,
- StyledBucketRow,
StyledBucketSizeCell,
} from './BucketTableRow.styles';
@@ -68,25 +68,20 @@ export const BucketTableRow = (props: BucketTableRowProps) => {
const typeLabel = isLegacy ? 'Legacy' : 'Standard';
return (
-
+
-
-
-
-
-
- {label}{' '}
-
-
-
-
+
+
+
+ {label}
+
{hostname}
-
-
+
+
@@ -134,6 +129,6 @@ export const BucketTableRow = (props: BucketTableRowProps) => {
onRemove={onRemove}
/>
-
+
);
};
diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/OveragePricing.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/OveragePricing.tsx
index f8c25b11f11..7b7b1b2513b 100644
--- a/packages/manager/src/features/ObjectStorage/BucketLanding/OveragePricing.tsx
+++ b/packages/manager/src/features/ObjectStorage/BucketLanding/OveragePricing.tsx
@@ -1,7 +1,7 @@
+import { Box } from '@linode/ui';
import { styled } from '@mui/material/styles';
import React from 'react';
-import { Box } from 'src/components/Box';
import { CircleProgress } from 'src/components/CircleProgress';
import { TextTooltip } from 'src/components/TextTooltip';
import { Typography } from 'src/components/Typography';
diff --git a/packages/manager/src/features/PlacementGroups/PlacementGroupPolicyRadioGroup.tsx b/packages/manager/src/features/PlacementGroups/PlacementGroupPolicyRadioGroup.tsx
index f50763a2c44..27d4b7f299d 100644
--- a/packages/manager/src/features/PlacementGroups/PlacementGroupPolicyRadioGroup.tsx
+++ b/packages/manager/src/features/PlacementGroups/PlacementGroupPolicyRadioGroup.tsx
@@ -1,6 +1,6 @@
+import { Box } from '@linode/ui';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { FormControlLabel } from 'src/components/FormControlLabel';
import { FormLabel } from 'src/components/FormLabel';
import { Notice } from 'src/components/Notice/Notice';
diff --git a/packages/manager/src/features/PlacementGroups/PlacementGroupTypeSelect.tsx b/packages/manager/src/features/PlacementGroups/PlacementGroupTypeSelect.tsx
index b6bc9535536..e9091436fd2 100644
--- a/packages/manager/src/features/PlacementGroups/PlacementGroupTypeSelect.tsx
+++ b/packages/manager/src/features/PlacementGroups/PlacementGroupTypeSelect.tsx
@@ -1,9 +1,9 @@
+import { Tooltip } from '@linode/ui';
import * as React from 'react';
import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
import { Link } from 'src/components/Link';
import { ListItem } from 'src/components/ListItem';
-import { Tooltip } from 'src/components/Tooltip';
import { Typography } from 'src/components/Typography';
import { PLACEMENT_GROUPS_DOCS_LINK } from './constants';
diff --git a/packages/manager/src/features/PlacementGroups/PlacementGroupsAssignLinodesDrawer.tsx b/packages/manager/src/features/PlacementGroups/PlacementGroupsAssignLinodesDrawer.tsx
index a7662421b52..8aaa0e6eae6 100644
--- a/packages/manager/src/features/PlacementGroups/PlacementGroupsAssignLinodesDrawer.tsx
+++ b/packages/manager/src/features/PlacementGroups/PlacementGroupsAssignLinodesDrawer.tsx
@@ -1,12 +1,12 @@
import {
- PLACEMENT_GROUP_TYPES,
PLACEMENT_GROUP_POLICIES,
+ PLACEMENT_GROUP_TYPES,
} from '@linode/api-v4';
+import { Box } from '@linode/ui';
import { useSnackbar } from 'notistack';
import * as React from 'react';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
-import { Box } from 'src/components/Box';
import { DescriptionList } from 'src/components/DescriptionList/DescriptionList';
import { Divider } from 'src/components/Divider';
import { Drawer } from 'src/components/Drawer';
diff --git a/packages/manager/src/features/PlacementGroups/PlacementGroupsCreateDrawer.tsx b/packages/manager/src/features/PlacementGroups/PlacementGroupsCreateDrawer.tsx
index 16469439483..03388e23ac4 100644
--- a/packages/manager/src/features/PlacementGroups/PlacementGroupsCreateDrawer.tsx
+++ b/packages/manager/src/features/PlacementGroups/PlacementGroupsCreateDrawer.tsx
@@ -43,7 +43,7 @@ import type {
Region,
} from '@linode/api-v4';
import type { FormikHelpers } from 'formik';
-import type { DisableRegionOption } from 'src/components/RegionSelect/RegionSelect.types';
+import type { DisableItemOption } from 'src/components/ListItemOption';
export const PlacementGroupsCreateDrawer = (
props: PlacementGroupsCreateDrawerProps
@@ -156,7 +156,7 @@ export const PlacementGroupsCreateDrawer = (
selectedRegion
)}`;
- const disabledRegions = regions?.reduce>(
+ const disabledRegions = regions?.reduce>(
(acc, region) => {
const isRegionAtCapacity = hasRegionReachedPlacementGroupCapacity({
allPlacementGroups: allPlacementGroupsInRegion,
diff --git a/packages/manager/src/features/PlacementGroups/PlacementGroupsDetail/PlacementGroupsSummary/PlacementGroupsSummary.tsx b/packages/manager/src/features/PlacementGroups/PlacementGroupsDetail/PlacementGroupsSummary/PlacementGroupsSummary.tsx
index 1778518729c..d5b3489c74b 100644
--- a/packages/manager/src/features/PlacementGroups/PlacementGroupsDetail/PlacementGroupsSummary/PlacementGroupsSummary.tsx
+++ b/packages/manager/src/features/PlacementGroups/PlacementGroupsDetail/PlacementGroupsSummary/PlacementGroupsSummary.tsx
@@ -1,16 +1,16 @@
import {
- PLACEMENT_GROUP_TYPES,
PLACEMENT_GROUP_POLICIES,
+ PLACEMENT_GROUP_TYPES,
} from '@linode/api-v4';
+import { Box } from '@linode/ui';
import { useTheme } from '@mui/material';
import { styled } from '@mui/material/styles';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { DescriptionList } from 'src/components/DescriptionList/DescriptionList';
import { Link } from 'src/components/Link';
import { Notice } from 'src/components/Notice/Notice';
-import { Paper } from 'src/components/Paper';
+import { Paper } from '@linode/ui';
import { Typography } from 'src/components/Typography';
import {
diff --git a/packages/manager/src/features/PlacementGroups/PlacementGroupsDetailPanel.tsx b/packages/manager/src/features/PlacementGroups/PlacementGroupsDetailPanel.tsx
index 29aaa7f91f6..5b6d526718c 100644
--- a/packages/manager/src/features/PlacementGroups/PlacementGroupsDetailPanel.tsx
+++ b/packages/manager/src/features/PlacementGroups/PlacementGroupsDetailPanel.tsx
@@ -1,7 +1,7 @@
+import { Box } from '@linode/ui';
import { useTheme } from '@mui/material/styles';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { ListItem } from 'src/components/ListItem';
import { Notice } from 'src/components/Notice/Notice';
diff --git a/packages/manager/src/features/PlacementGroups/PlacementGroupsLanding/PlacementGroupsLandingEmptyState.tsx b/packages/manager/src/features/PlacementGroups/PlacementGroupsLanding/PlacementGroupsLandingEmptyState.tsx
index 0c6c478ae93..cf0331b9095 100644
--- a/packages/manager/src/features/PlacementGroups/PlacementGroupsLanding/PlacementGroupsLandingEmptyState.tsx
+++ b/packages/manager/src/features/PlacementGroups/PlacementGroupsLanding/PlacementGroupsLandingEmptyState.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
-import PlacementGroups from 'src/assets/icons/entityIcons/placement-groups.svg';
+import LinodeIcon from 'src/assets/icons/entityIcons/linode.svg';
import { ResourcesSection } from 'src/components/EmptyLandingPageResources/ResourcesSection';
import { sendEvent } from 'src/utilities/analytics/utils';
@@ -37,7 +37,7 @@ export const PlacementGroupsLandingEmptyState = ({
]}
gettingStartedGuidesData={gettingStartedGuides}
headers={headers}
- icon={PlacementGroups}
+ icon={LinodeIcon}
linkAnalyticsEvent={linkAnalyticsEvent}
/>
);
diff --git a/packages/manager/src/features/Profile/APITokens/APITokenTable.tsx b/packages/manager/src/features/Profile/APITokens/APITokenTable.tsx
index e8f84e4f94d..f49ea6b0532 100644
--- a/packages/manager/src/features/Profile/APITokens/APITokenTable.tsx
+++ b/packages/manager/src/features/Profile/APITokens/APITokenTable.tsx
@@ -1,10 +1,9 @@
+import { Box, Paper } from '@linode/ui';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { DateTimeDisplay } from 'src/components/DateTimeDisplay';
import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter';
-import { Paper } from 'src/components/Paper';
import { Table } from 'src/components/Table';
import { TableBody } from 'src/components/TableBody';
import { TableCell } from 'src/components/TableCell';
diff --git a/packages/manager/src/features/Profile/APITokens/CreateAPITokenDrawer.tsx b/packages/manager/src/features/Profile/APITokens/CreateAPITokenDrawer.tsx
index 2156c1524fc..58a0b8cc935 100644
--- a/packages/manager/src/features/Profile/APITokens/CreateAPITokenDrawer.tsx
+++ b/packages/manager/src/features/Profile/APITokens/CreateAPITokenDrawer.tsx
@@ -1,3 +1,4 @@
+import { FormControl, FormHelperText } from '@linode/ui';
import { useFormik } from 'formik';
import { DateTime } from 'luxon';
import * as React from 'react';
@@ -5,8 +6,6 @@ import * as React from 'react';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
import { Drawer } from 'src/components/Drawer';
-import { FormControl } from 'src/components/FormControl';
-import { FormHelperText } from 'src/components/FormHelperText';
import { Notice } from 'src/components/Notice/Notice';
import { Radio } from 'src/components/Radio/Radio';
import { TableBody } from 'src/components/TableBody';
diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/AuthenticationSettings.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/AuthenticationSettings.tsx
index 72d8c3bdeef..efba74259d0 100644
--- a/packages/manager/src/features/Profile/AuthenticationSettings/AuthenticationSettings.tsx
+++ b/packages/manager/src/features/Profile/AuthenticationSettings/AuthenticationSettings.tsx
@@ -1,3 +1,4 @@
+import { Paper } from '@linode/ui';
import { styled } from '@mui/material/styles';
import { createLazyRoute } from '@tanstack/react-router';
import * as React from 'react';
@@ -8,7 +9,6 @@ import { Divider } from 'src/components/Divider';
import { DocumentTitleSegment } from 'src/components/DocumentTitle';
import { ErrorState } from 'src/components/ErrorState/ErrorState';
import { Link } from 'src/components/Link';
-import { Paper } from 'src/components/Paper';
import { Typography } from 'src/components/Typography';
import { useProfile } from 'src/queries/profile/profile';
diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.styles.ts b/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.styles.ts
index d6caaa14b50..cd5b9f75a26 100644
--- a/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.styles.ts
+++ b/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.styles.ts
@@ -1,11 +1,9 @@
+import { Box, FormHelperText, omittedProps } from '@linode/ui';
import { styled } from '@mui/material/styles';
import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
-import { Box } from 'src/components/Box';
-import { FormHelperText } from 'src/components/FormHelperText';
import { TextField } from 'src/components/TextField';
import { Typography } from 'src/components/Typography';
-import { omittedProps } from 'src/utilities/omittedProps';
export const StyledCodeSentMessageBox = styled(Box, {
label: 'StyledCodeSentMessageBox',
diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.tsx
index c0d5334bc02..89d4f78fd56 100644
--- a/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.tsx
+++ b/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.tsx
@@ -1,13 +1,13 @@
+import { Box, InputAdornment } from '@linode/ui';
import { useQueryClient } from '@tanstack/react-query';
import { useFormik } from 'formik';
import { parsePhoneNumber } from 'libphonenumber-js';
import { useSnackbar } from 'notistack';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
-import { InputAdornment } from 'src/components/InputAdornment';
import { LinkButton } from 'src/components/LinkButton';
+import { MaskableText } from 'src/components/MaskableText/MaskableText';
import { TextField } from 'src/components/TextField';
import { Typography } from 'src/components/Typography';
import {
@@ -215,9 +215,14 @@ export const PhoneVerification = ({
- {profile?.verified_phone_number
- ? getFormattedNumber(profile.verified_phone_number)
- : 'No Phone Number'}
+
{
return (
<>
{label}
-
- {questionResponse?.question}
+
+
Edit
diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/SecurityQuestions/QuestionAndAnswerPair.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/SecurityQuestions/QuestionAndAnswerPair.tsx
index cab647664d9..b04e6601611 100644
--- a/packages/manager/src/features/Profile/AuthenticationSettings/SecurityQuestions/QuestionAndAnswerPair.tsx
+++ b/packages/manager/src/features/Profile/AuthenticationSettings/SecurityQuestions/QuestionAndAnswerPair.tsx
@@ -1,8 +1,7 @@
+import { Box } from '@linode/ui';
import { styled } from '@mui/material/styles';
import * as React from 'react';
-import { Box } from 'src/components/Box';
-
import { Answer } from './Answer';
import { Question } from './Question';
diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/SecurityQuestions/SecurityQuestions.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/SecurityQuestions/SecurityQuestions.tsx
index d47fd939c4e..e0cba8feed9 100644
--- a/packages/manager/src/features/Profile/AuthenticationSettings/SecurityQuestions/SecurityQuestions.tsx
+++ b/packages/manager/src/features/Profile/AuthenticationSettings/SecurityQuestions/SecurityQuestions.tsx
@@ -1,9 +1,9 @@
+import { Box } from '@linode/ui';
import { styled } from '@mui/material/styles';
import { useFormik } from 'formik';
import { useSnackbar } from 'notistack';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { CircleProgress } from 'src/components/CircleProgress';
import { Link } from 'src/components/Link';
diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.styles.ts b/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.styles.ts
index 928294c5572..f0cc13c638c 100644
--- a/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.styles.ts
+++ b/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.styles.ts
@@ -1,10 +1,10 @@
-import Grid from '@mui/material/Unstable_Grid2';
+import { Paper } from '@linode/ui';
import { styled } from '@mui/material/styles';
+import Grid from '@mui/material/Unstable_Grid2';
import { Button } from 'src/components/Button/Button';
import { Notice } from 'src/components/Notice/Notice';
import { Typography } from 'src/components/Typography';
-import { Paper } from 'src/components/Paper';
export const StyledRootContainer = styled(Paper, {
label: 'StyledRootContainer',
diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.tsx
index 4520dc713d6..caed45a3d8f 100644
--- a/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.tsx
+++ b/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.tsx
@@ -1,13 +1,12 @@
-import { TPAProvider } from '@linode/api-v4/lib/profile';
-import Grid from '@mui/material/Unstable_Grid2';
+import { Box } from '@linode/ui';
import { useTheme } from '@mui/material/styles';
+import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';
import EnabledIcon from 'src/assets/icons/checkmark-enabled.svg';
import AkamaiWaveOnlyIcon from 'src/assets/icons/providers/akamai-logo-rgb-waveOnly.svg';
import GitHubIcon from 'src/assets/icons/providers/github-logo.svg';
import GoogleIcon from 'src/assets/icons/providers/google-logo.svg';
-import { Box } from 'src/components/Box';
import { Divider } from 'src/components/Divider';
import { Link } from 'src/components/Link';
import { Typography } from 'src/components/Typography';
@@ -23,6 +22,8 @@ import {
StyledRootContainer,
} from './TPAProviders.styles';
+import type { TPAProvider } from '@linode/api-v4/lib/profile';
+
interface Props {
authType: TPAProvider;
}
diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/TrustedDevices.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/TrustedDevices.tsx
index 3279aa424e5..4a8773e9c0f 100644
--- a/packages/manager/src/features/Profile/AuthenticationSettings/TrustedDevices.tsx
+++ b/packages/manager/src/features/Profile/AuthenticationSettings/TrustedDevices.tsx
@@ -1,9 +1,9 @@
-import { Theme } from '@mui/material/styles';
import * as React from 'react';
import { makeStyles } from 'tss-react/mui';
import { DateTimeDisplay } from 'src/components/DateTimeDisplay';
import { InlineMenuAction } from 'src/components/InlineMenuAction/InlineMenuAction';
+import { MaskableText } from 'src/components/MaskableText/MaskableText';
import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter';
import { Table } from 'src/components/Table';
import { TableBody } from 'src/components/TableBody';
@@ -21,6 +21,8 @@ import { useTrustedDevicesQuery } from 'src/queries/profile/profile';
import { RevokeTrustedDeviceDialog } from './RevokeTrustedDevicesDialog';
+import type { Theme } from '@mui/material/styles';
+
const useStyles = makeStyles()((theme: Theme) => ({
copy: {
lineHeight: '20px',
@@ -87,8 +89,12 @@ const TrustedDevices = () => {
return data?.data.map((device) => {
return (
- {device.user_agent}
- {device.last_remote_addr}
+
+
+
+
+
+
diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/ConfirmToken.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/ConfirmToken.tsx
index f81eed13103..2584b7bd3d1 100644
--- a/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/ConfirmToken.tsx
+++ b/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/ConfirmToken.tsx
@@ -1,7 +1,7 @@
+import { Box } from '@linode/ui';
import { styled } from '@mui/material/styles';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { Notice } from 'src/components/Notice/Notice';
import { TextField } from 'src/components/TextField';
diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/QRCodeForm.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/QRCodeForm.tsx
index e2d79add7c6..cdd266234ad 100644
--- a/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/QRCodeForm.tsx
+++ b/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/QRCodeForm.tsx
@@ -42,7 +42,7 @@ const StyledInstructions = styled(Typography, {
const StyledQRCodeContainer = styled('div', {
label: 'StyledQRCodeContainer',
})(({ theme }) => ({
- border: `5px solid ${theme.colorTokens.Neutrals.White}`,
+ border: `5px solid ${theme.tokens.color.Neutrals.White}`,
display: 'inline-block',
margin: `${theme.spacing(2)} 0`,
}));
diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/TwoFactorToggle.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/TwoFactorToggle.tsx
index 941310500ec..0bfeea24e34 100644
--- a/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/TwoFactorToggle.tsx
+++ b/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/TwoFactorToggle.tsx
@@ -1,6 +1,6 @@
+import { FormControl } from '@linode/ui';
import * as React from 'react';
-import { FormControl } from 'src/components/FormControl';
import { FormControlLabel } from 'src/components/FormControlLabel';
import { Toggle } from 'src/components/Toggle/Toggle';
diff --git a/packages/manager/src/features/Profile/DisplaySettings/AvatarForm.test.tsx b/packages/manager/src/features/Profile/DisplaySettings/AvatarForm.test.tsx
new file mode 100644
index 00000000000..7e121bf12c9
--- /dev/null
+++ b/packages/manager/src/features/Profile/DisplaySettings/AvatarForm.test.tsx
@@ -0,0 +1,31 @@
+import userEvent from '@testing-library/user-event';
+import React from 'react';
+
+import { renderWithTheme } from 'src/utilities/testHelpers';
+
+import { AvatarForm } from './AvatarForm';
+
+describe('AvatarForm', () => {
+ it('renders a label, button, and avatar', () => {
+ const { getByRole, getByTestId, getByText } = renderWithTheme(
+
+ );
+
+ expect(getByText('Avatar')).toBeVisible();
+ expect(getByTestId('avatar')).toBeVisible();
+ expect(getByRole('button', { name: 'Change Avatar Color' })).toBeVisible();
+ });
+
+ it('opens the avatar color dialog when the button is clicked', async () => {
+ const { getByRole } = renderWithTheme( );
+
+ const button = getByRole('button', { name: 'Change Avatar Color' });
+
+ expect(button).toBeVisible();
+ expect(button).toBeEnabled();
+
+ await userEvent.click(button);
+
+ expect(getByRole('heading', { name: 'Change Avatar Color' })).toBeVisible();
+ });
+});
diff --git a/packages/manager/src/features/Profile/DisplaySettings/AvatarForm.tsx b/packages/manager/src/features/Profile/DisplaySettings/AvatarForm.tsx
new file mode 100644
index 00000000000..87d29ba8c34
--- /dev/null
+++ b/packages/manager/src/features/Profile/DisplaySettings/AvatarForm.tsx
@@ -0,0 +1,55 @@
+import { Box } from '@linode/ui';
+import { styled } from '@mui/material/styles';
+import React from 'react';
+
+import { Avatar } from 'src/components/Avatar/Avatar';
+import { Button } from 'src/components/Button/Button';
+import { Typography } from 'src/components/Typography';
+
+import { AvatarColorPickerDialog } from './AvatarColorPickerDialog';
+
+export const AvatarForm = () => {
+ const [
+ isColorPickerDialogOpen,
+ setAvatarColorPickerDialogOpen,
+ ] = React.useState(false);
+
+ return (
+ ({
+ display: 'flex',
+ gap: 2,
+ marginTop: theme.spacing(),
+ })}
+ >
+
+
+
+ Avatar
+
+
+ Your avatar is automatically generated using the first character of
+ your username.
+
+
+
+ setAvatarColorPickerDialogOpen(false)}
+ open={isColorPickerDialogOpen}
+ />
+
+ );
+};
+
+const StyledProfileCopy = styled(Typography, {
+ label: 'StyledProfileCopy',
+})(({ theme }) => ({
+ marginBottom: theme.spacing(2),
+ marginTop: 4,
+ maxWidth: 360,
+}));
diff --git a/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.test.tsx b/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.test.tsx
index 2008bf9c339..ac8207f46ef 100644
--- a/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.test.tsx
+++ b/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.test.tsx
@@ -1,70 +1,15 @@
-import { screen } from '@testing-library/react';
import React from 'react';
-import { profileFactory } from 'src/factories/profile';
import { DisplaySettings } from 'src/features/Profile/DisplaySettings/DisplaySettings';
import { renderWithTheme } from 'src/utilities/testHelpers';
-const queryMocks = vi.hoisted(() => ({
- useProfile: vi.fn().mockReturnValue({}),
-}));
+describe('DisplaySettings', () => {
+ it('renders profile display sections', () => {
+ const { getByText } = renderWithTheme( );
-vi.mock('src/queries/profile/profile', async () => {
- const actual = await vi.importActual('src/queries/profile/profile');
- return {
- ...actual,
- useProfile: queryMocks.useProfile,
- };
-});
-
-describe('DisplaySettings component', () => {
- it('should disable SingleTextFieldForm components based on isProxyUser', () => {
- queryMocks.useProfile.mockReturnValue({
- data: profileFactory.build({ user_type: 'proxy' }),
- });
-
- renderWithTheme( );
-
- const usernameInput = screen.getByLabelText('Username');
- expect(usernameInput).toHaveAttribute('disabled');
-
- const emailInput = screen.getByLabelText('Email');
- expect(emailInput).toHaveAttribute('disabled');
- });
-
- it('Should only disable the SingleTextFieldForm components if the user is not a proxy user.', () => {
- queryMocks.useProfile.mockReturnValue({
- data: profileFactory.build({ user_type: 'child' }),
- });
-
- renderWithTheme( );
-
- const usernameInput = screen.getByLabelText('Username');
- expect(usernameInput).not.toHaveAttribute('disabled');
-
- const emailInput = screen.getByLabelText('Email');
- expect(emailInput).not.toHaveAttribute('disabled');
- });
-
- it('should disable the "Update Username" and "Update Email" buttons if the user is a proxy user.', async () => {
- queryMocks.useProfile.mockReturnValue({
- data: profileFactory.build({ user_type: 'proxy' }),
- });
-
- renderWithTheme( );
-
- const updateUsernameButton = screen
- .getByText('Update Username')
- .closest('button');
-
- const updateEmailButton = screen
- .getByText('Update Email')
- .closest('button');
-
- expect(updateUsernameButton).not.toHaveAttribute('disabled');
- expect(updateUsernameButton).toHaveAttribute('aria-disabled', 'true');
-
- expect(updateEmailButton).not.toHaveAttribute('disabled');
- expect(updateEmailButton).toHaveAttribute('aria-disabled', 'true');
+ expect(getByText('Avatar')).toBeVisible();
+ expect(getByText('Username')).toBeVisible();
+ expect(getByText('Email')).toBeVisible();
+ expect(getByText('Timezone')).toBeVisible();
});
});
diff --git a/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.tsx b/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.tsx
index 5bd869cabef..e1812a53fc5 100644
--- a/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.tsx
+++ b/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.tsx
@@ -1,164 +1,33 @@
-import { updateUser } from '@linode/api-v4/lib/account';
-import { styled, useTheme } from '@mui/material/styles';
+import { Paper } from '@linode/ui';
import { createLazyRoute } from '@tanstack/react-router';
-import * as React from 'react';
-import { useSelector } from 'react-redux';
-import { useLocation } from 'react-router-dom';
-import { v4 } from 'uuid';
+import React from 'react';
-import { Avatar } from 'src/components/Avatar/Avatar';
-import { Box } from 'src/components/Box';
-import { Button } from 'src/components/Button/Button';
import { Divider } from 'src/components/Divider';
-import { Paper } from 'src/components/Paper';
-import { SingleTextFieldForm } from 'src/components/SingleTextFieldForm/SingleTextFieldForm';
-import { Typography } from 'src/components/Typography';
-import { RESTRICTED_FIELD_TOOLTIP } from 'src/features/Account/constants';
-import { useNotificationsQuery } from 'src/queries/account/notifications';
-import { useMutateProfile, useProfile } from 'src/queries/profile/profile';
+import { Stack } from 'src/components/Stack';
+import { useProfile } from 'src/queries/profile/profile';
-import { AvatarColorPickerDialog } from './AvatarColorPickerDialog';
+import { AvatarForm } from './AvatarForm';
+import { EmailForm } from './EmailForm';
import { TimezoneForm } from './TimezoneForm';
-
-import type { ApplicationState } from 'src/store';
+import { UsernameForm } from './UsernameForm';
export const DisplaySettings = () => {
- const theme = useTheme();
- const { mutateAsync: updateProfile } = useMutateProfile();
- const { data: profile, refetch: requestProfile } = useProfile();
- const { data: notifications, refetch } = useNotificationsQuery();
- const loggedInAsCustomer = useSelector(
- (state: ApplicationState) => state.authentication.loggedInAsCustomer
- );
- const location = useLocation<{ focusEmail: boolean }>();
- const emailRef = React.createRef();
+ const { data: profile } = useProfile();
const isProxyUser = profile?.user_type === 'proxy';
- const [
- isColorPickerDialogOpen,
- setAvatarColorPickerDialogOpen,
- ] = React.useState(false);
-
- React.useEffect(() => {
- if (location.state?.focusEmail && emailRef.current) {
- emailRef.current.focus();
- emailRef.current.scrollIntoView();
- }
- }, [emailRef, location.state]);
-
- // Used as React keys to force-rerender forms.
- const [emailResetToken, setEmailResetToken] = React.useState(v4());
- const [usernameResetToken, setUsernameResetToken] = React.useState(v4());
-
- const updateUsername = (newUsername: string) => {
- setEmailResetToken(v4());
- // Default to empty string... but I don't believe this is possible.
- return updateUser(profile?.username ?? '', {
- username: newUsername,
- });
- };
-
- const updateEmail = (newEmail: string) => {
- setUsernameResetToken(v4());
- return updateProfile({ email: newEmail });
- };
-
- const tooltipForDisabledUsernameField = profile?.restricted
- ? 'Restricted users cannot update their username. Please contact an account administrator.'
- : isProxyUser
- ? RESTRICTED_FIELD_TOOLTIP
- : undefined;
-
- const tooltipForDisabledEmailField = isProxyUser
- ? RESTRICTED_FIELD_TOOLTIP
- : undefined;
-
return (
- {!isProxyUser && (
- <>
-
-
-
-
- Avatar
-
-
- Your avatar is automatically generated using the first character
- of your username.
-
-
-
-
-
-
- >
- )}
-
-
-
- {
- // If there's a "user_email_bounce" notification for this user, and
- // the user has just updated their email, re-request notifications to
- // potentially clear the email bounce notification.
- const hasUserEmailBounceNotification = notifications?.find(
- (thisNotification) => thisNotification.type === 'user_email_bounce'
- );
- if (hasUserEmailBounceNotification) {
- refetch();
- }
- }}
- disabled={isProxyUser}
- initialValue={profile?.email}
- inputRef={emailRef}
- key={emailResetToken}
- label="Email"
- submitForm={updateEmail}
- tooltipText={tooltipForDisabledEmailField}
- trimmed
- type="email"
- />
-
-
- setAvatarColorPickerDialogOpen(false)}
- open={isColorPickerDialogOpen}
- />
+ } spacing={3}>
+ {!isProxyUser && }
+
+
+
+
);
};
-const StyledProfileCopy = styled(Typography, {
- label: 'StyledProfileCopy',
-})(({ theme }) => ({
- marginBottom: theme.spacing(2),
- marginTop: 4,
- maxWidth: 360,
-}));
-
export const displaySettingsLazyRoute = createLazyRoute('/profile/display')({
component: DisplaySettings,
});
diff --git a/packages/manager/src/features/Profile/DisplaySettings/EmailForm.test.tsx b/packages/manager/src/features/Profile/DisplaySettings/EmailForm.test.tsx
new file mode 100644
index 00000000000..f381a503950
--- /dev/null
+++ b/packages/manager/src/features/Profile/DisplaySettings/EmailForm.test.tsx
@@ -0,0 +1,64 @@
+import { waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import React from 'react';
+
+import { profileFactory } from 'src/factories';
+import { HttpResponse, http, server } from 'src/mocks/testServer';
+import { renderWithTheme } from 'src/utilities/testHelpers';
+
+import { EmailForm } from './EmailForm';
+
+describe('EmailForm', () => {
+ it('renders a label and input', () => {
+ const { getByLabelText, getByText } = renderWithTheme( );
+
+ expect(getByLabelText('Email')).toBeVisible();
+ expect(getByText('Update Email')).toBeVisible();
+ });
+
+ it("populates the form with the current user's email", async () => {
+ const profile = profileFactory.build({ username: 'myself@linode.com' });
+
+ server.use(http.get('*/v4/profile', () => HttpResponse.json(profile)));
+
+ const { findByDisplayValue } = renderWithTheme( );
+
+ await findByDisplayValue(profile.email);
+ });
+
+ it('disables the input if the user is a proxy user', async () => {
+ const profile = profileFactory.build({
+ user_type: 'proxy',
+ });
+
+ server.use(http.get('*/v4/profile', () => HttpResponse.json(profile)));
+
+ const { getByLabelText } = renderWithTheme( );
+
+ await waitFor(() => {
+ expect(getByLabelText('Email')).toBeDisabled();
+ });
+
+ expect(getByLabelText('This field can’t be modified.')).toBeVisible();
+ });
+
+ it('enables the save button when the user makes a change to the email', async () => {
+ const profile = profileFactory.build({ email: 'user@linode.com' });
+
+ server.use(http.get('*/v4/profile', () => HttpResponse.json(profile)));
+
+ const { findByDisplayValue, getByLabelText, getByText } = renderWithTheme(
+
+ );
+
+ await findByDisplayValue(profile.email);
+
+ const saveButton = getByText('Update Email').closest('button');
+
+ expect(saveButton).toBeDisabled();
+
+ await userEvent.type(getByLabelText('Email'), 'user-1@linode.com');
+
+ expect(saveButton).toBeEnabled();
+ });
+});
diff --git a/packages/manager/src/features/Profile/DisplaySettings/EmailForm.tsx b/packages/manager/src/features/Profile/DisplaySettings/EmailForm.tsx
new file mode 100644
index 00000000000..039c2c896a0
--- /dev/null
+++ b/packages/manager/src/features/Profile/DisplaySettings/EmailForm.tsx
@@ -0,0 +1,97 @@
+import { useSnackbar } from 'notistack';
+import React from 'react';
+import { Controller, useForm } from 'react-hook-form';
+import { useLocation } from 'react-router-dom';
+
+import { Button } from 'src/components/Button/Button';
+import { TextField } from 'src/components/TextField';
+import { RESTRICTED_FIELD_TOOLTIP } from 'src/features/Account/constants';
+import { useMutateProfile, useProfile } from 'src/queries/profile/profile';
+
+import { SingleTextFieldFormContainer } from './TimezoneForm';
+
+import type { Profile } from '@linode/api-v4';
+
+type Values = Pick;
+
+export const EmailForm = () => {
+ const { data: profile } = useProfile();
+ const { mutateAsync: updateProfile } = useMutateProfile();
+ const { enqueueSnackbar } = useSnackbar();
+
+ const location = useLocation<{ focusEmail: boolean }>();
+ const emailRef = React.createRef();
+
+ React.useEffect(() => {
+ if (location.state?.focusEmail && emailRef.current) {
+ emailRef.current.focus();
+ emailRef.current.scrollIntoView();
+ }
+ }, [emailRef, location.state]);
+
+ const values = { email: profile?.email ?? '' };
+
+ const {
+ control,
+ formState: { isDirty, isSubmitting },
+ handleSubmit,
+ setError,
+ } = useForm({
+ defaultValues: values,
+ values,
+ });
+
+ const tooltipForDisabledEmailField =
+ profile?.user_type === 'proxy' ? RESTRICTED_FIELD_TOOLTIP : undefined;
+
+ const onSubmit = async (values: Values) => {
+ try {
+ await updateProfile(values);
+ enqueueSnackbar({
+ message: 'Email updated successfully.',
+ variant: 'success',
+ });
+ } catch (error) {
+ setError('email', { message: error[0].reason });
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/packages/manager/src/features/Profile/DisplaySettings/TimezoneForm.test.tsx b/packages/manager/src/features/Profile/DisplaySettings/TimezoneForm.test.tsx
index eff34420a91..2093cd8887e 100644
--- a/packages/manager/src/features/Profile/DisplaySettings/TimezoneForm.test.tsx
+++ b/packages/manager/src/features/Profile/DisplaySettings/TimezoneForm.test.tsx
@@ -1,63 +1,65 @@
-import { waitForElementToBeRemoved } from '@testing-library/react';
+import { waitFor } from '@testing-library/react';
import * as React from 'react';
import { profileFactory } from 'src/factories';
import { HttpResponse, http, server } from 'src/mocks/testServer';
-import { queryClientFactory } from 'src/queries/base';
import { renderWithTheme } from 'src/utilities/testHelpers';
import { TimezoneForm, getOptionLabel } from './TimezoneForm';
-const queryClient = queryClientFactory();
-
describe('Timezone change form', () => {
- // Use the MSW to mock a profile with America/New_York as the timezone
- // for this specific suite of tests
- server.use(
- http.get('*/profile', () => {
- return HttpResponse.json(
- profileFactory.build({ timezone: 'America/New_York' })
- );
- })
- );
-
- it('should render input label', async () => {
- const { getByTestId, getByText } = renderWithTheme(
- ,
- { queryClient }
+ beforeEach(() => {
+ // Use the MSW to mock a profile with America/New_York as the timezone
+ // for this specific suite of tests
+ server.use(
+ http.get('*/profile', () => {
+ return HttpResponse.json(
+ profileFactory.build({ timezone: 'America/New_York' })
+ );
+ })
);
+ });
- // This component depends on the /profile to be loaded. Wait for
- // loading to finish before we check anything.
- await waitForElementToBeRemoved(getByTestId('circle-progress'));
+ it('should render input label', () => {
+ const { getByText } = renderWithTheme( );
expect(getByText('Timezone')).toBeInTheDocument();
});
- it('should show a message if an admin is logged in as a customer', () => {
- const { getByTestId } = renderWithTheme(
- ,
- { queryClient }
- );
+ it('should show a message if an admin is logged in as a customer', async () => {
+ const { getByTestId } = renderWithTheme( , {
+ customStore: { authentication: { loggedInAsCustomer: true } },
+ });
expect(getByTestId('admin-notice')).toBeInTheDocument();
});
- it('should not show a message if the user is logged in normally', () => {
- const { queryByTestId } = renderWithTheme(
- ,
- { queryClient }
- );
+ it('should not show a message if the user is logged in normally', async () => {
+ const { queryByTestId } = renderWithTheme( );
expect(queryByTestId('admin-notice')).not.toBeInTheDocument();
});
- it("should include text with the user's current time zone", async () => {
- const { queryByTestId } = renderWithTheme(
- ,
- { queryClient }
- );
- expect(queryByTestId('admin-notice')).toHaveTextContent('America/New_York');
+ it("should include text with the user's current time zone in the admin warning", async () => {
+ const { queryByTestId } = renderWithTheme( , {
+ customStore: { authentication: { loggedInAsCustomer: true } },
+ });
+
+ await waitFor(() => {
+ expect(queryByTestId('admin-notice')).toHaveTextContent(
+ 'America/New_York'
+ );
+ });
+ });
+
+ it("should show the user's currently selected timezone", async () => {
+ const { getByLabelText } = renderWithTheme( );
+
+ await waitFor(() => {
+ expect(getByLabelText('Timezone')).toHaveDisplayValue(
+ /Eastern Time - New York/
+ );
+ });
});
});
diff --git a/packages/manager/src/features/Profile/DisplaySettings/TimezoneForm.tsx b/packages/manager/src/features/Profile/DisplaySettings/TimezoneForm.tsx
index 7fe803e8c18..43c8e8053ea 100644
--- a/packages/manager/src/features/Profile/DisplaySettings/TimezoneForm.tsx
+++ b/packages/manager/src/features/Profile/DisplaySettings/TimezoneForm.tsx
@@ -2,17 +2,16 @@ import { styled } from '@mui/material/styles';
import { DateTime } from 'luxon';
import { useSnackbar } from 'notistack';
import * as React from 'react';
+import { Controller, useForm } from 'react-hook-form';
import { timezones } from 'src/assets/timezones/timezones';
import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
import { Button } from 'src/components/Button/Button';
-import { CircleProgress } from 'src/components/CircleProgress';
import { Notice } from 'src/components/Notice/Notice';
+import { useAuthentication } from 'src/hooks/useAuthentication';
import { useMutateProfile, useProfile } from 'src/queries/profile/profile';
-interface Props {
- loggedInAsCustomer: boolean;
-}
+import type { Profile } from '@linode/api-v4';
type Timezone = typeof timezones[number];
@@ -40,68 +39,82 @@ const getTimezoneOptions = () => {
const timezoneOptions = getTimezoneOptions();
-export const TimezoneForm = (props: Props) => {
- const { loggedInAsCustomer } = props;
+type Values = Pick;
+
+export const TimezoneForm = () => {
+ const { loggedInAsCustomer } = useAuthentication();
const { enqueueSnackbar } = useSnackbar();
const { data: profile } = useProfile();
- const { error, isPending, mutateAsync: updateProfile } = useMutateProfile();
+ const { mutateAsync: updateProfile } = useMutateProfile();
- const [timezoneValue, setTimezoneValue] = React.useState(profile?.timezone);
+ const values = { timezone: profile?.timezone ?? '' };
- const onSubmit = () => {
- if (!timezoneValue) {
- enqueueSnackbar('Please select a valid timezone.', { variant: 'error' });
- }
+ const {
+ control,
+ formState: { isDirty, isSubmitting },
+ handleSubmit,
+ setError,
+ } = useForm({
+ defaultValues: values,
+ values,
+ });
- updateProfile({ timezone: timezoneValue }).then(() => {
- enqueueSnackbar('Successfully updated timezone', { variant: 'success' });
- });
+ const onSubmit = async (values: Values) => {
+ try {
+ await updateProfile(values);
+ enqueueSnackbar('Successfully updated timezone.', { variant: 'success' });
+ } catch (error) {
+ setError('timezone', { message: error[0].reason });
+ }
};
- const disabled = !timezoneValue || profile?.timezone === timezoneValue;
-
- if (!profile) {
- return ;
- }
-
return (
- <>
+
);
};
-const TimezoneFormContainer = styled('div', {
- label: 'TimezoneFormContainer',
+export const SingleTextFieldFormContainer = styled('div', {
+ label: 'SingleTextFieldFormContainer',
})(({ theme }) => ({
alignItems: 'flex-end',
display: 'flex',
diff --git a/packages/manager/src/features/Profile/DisplaySettings/UsernameForm.test.tsx b/packages/manager/src/features/Profile/DisplaySettings/UsernameForm.test.tsx
new file mode 100644
index 00000000000..d0cd4bd5a8f
--- /dev/null
+++ b/packages/manager/src/features/Profile/DisplaySettings/UsernameForm.test.tsx
@@ -0,0 +1,83 @@
+import { waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import React from 'react';
+
+import { profileFactory } from 'src/factories';
+import { HttpResponse, http, server } from 'src/mocks/testServer';
+import { renderWithTheme } from 'src/utilities/testHelpers';
+
+import { UsernameForm } from './UsernameForm';
+
+describe('UsernameForm', () => {
+ it('renders a label and input', () => {
+ const { getByLabelText, getByText } = renderWithTheme( );
+
+ expect(getByLabelText('Username')).toBeVisible();
+ expect(getByText('Update Username')).toBeVisible();
+ });
+
+ it("populates the form with the current user's username", async () => {
+ const profile = profileFactory.build({ username: 'my-linode-username' });
+
+ server.use(http.get('*/v4/profile', () => HttpResponse.json(profile)));
+
+ const { findByDisplayValue } = renderWithTheme( );
+
+ await findByDisplayValue(profile.username);
+ });
+
+ it('disables the input if the user is restricted', async () => {
+ const profile = profileFactory.build({ restricted: true });
+
+ server.use(http.get('*/v4/profile', () => HttpResponse.json(profile)));
+
+ const { getByLabelText } = renderWithTheme( );
+
+ await waitFor(() => {
+ expect(getByLabelText('Username')).toBeDisabled();
+ });
+
+ expect(
+ getByLabelText(
+ 'Restricted users cannot update their username. Please contact an account administrator.'
+ )
+ ).toBeVisible();
+ });
+
+ it('disables the input if the user is a proxy user', async () => {
+ const profile = profileFactory.build({
+ restricted: false,
+ user_type: 'proxy',
+ });
+
+ server.use(http.get('*/v4/profile', () => HttpResponse.json(profile)));
+
+ const { getByLabelText } = renderWithTheme( );
+
+ await waitFor(() => {
+ expect(getByLabelText('Username')).toBeDisabled();
+ });
+
+ expect(getByLabelText('This field can’t be modified.')).toBeVisible();
+ });
+
+ it('enables the save button when the user makes a change to the username', async () => {
+ const profile = profileFactory.build({ username: 'my-linode-username' });
+
+ server.use(http.get('*/v4/profile', () => HttpResponse.json(profile)));
+
+ const { findByDisplayValue, getByLabelText, getByText } = renderWithTheme(
+
+ );
+
+ await findByDisplayValue(profile.username);
+
+ const saveButton = getByText('Update Username').closest('button');
+
+ expect(saveButton).toBeDisabled();
+
+ await userEvent.type(getByLabelText('Username'), 'my-linode-username-1');
+
+ expect(saveButton).toBeEnabled();
+ });
+});
diff --git a/packages/manager/src/features/Profile/DisplaySettings/UsernameForm.tsx b/packages/manager/src/features/Profile/DisplaySettings/UsernameForm.tsx
new file mode 100644
index 00000000000..0fef5ea1adc
--- /dev/null
+++ b/packages/manager/src/features/Profile/DisplaySettings/UsernameForm.tsx
@@ -0,0 +1,90 @@
+import { useSnackbar } from 'notistack';
+import React from 'react';
+import { Controller, useForm } from 'react-hook-form';
+
+import { Button } from 'src/components/Button/Button';
+import { TextField } from 'src/components/TextField';
+import { RESTRICTED_FIELD_TOOLTIP } from 'src/features/Account/constants';
+import { useUpdateUserMutation } from 'src/queries/account/users';
+import { useProfile } from 'src/queries/profile/profile';
+
+import { SingleTextFieldFormContainer } from './TimezoneForm';
+
+import type { Profile } from '@linode/api-v4';
+
+type Values = Pick;
+
+export const UsernameForm = () => {
+ const { data: profile } = useProfile();
+ const { mutateAsync: updateUser } = useUpdateUserMutation(
+ profile?.username ?? ''
+ );
+ const { enqueueSnackbar } = useSnackbar();
+
+ const values = { username: profile?.username ?? '' };
+
+ const {
+ control,
+ formState: { isDirty, isSubmitting },
+ handleSubmit,
+ setError,
+ } = useForm({
+ defaultValues: values,
+ values,
+ });
+
+ const tooltipForDisabledUsernameField = profile?.restricted
+ ? 'Restricted users cannot update their username. Please contact an account administrator.'
+ : profile?.user_type === 'proxy'
+ ? RESTRICTED_FIELD_TOOLTIP
+ : undefined;
+
+ const onSubmit = async (values: Values) => {
+ try {
+ await updateUser(values);
+ enqueueSnackbar({
+ message: 'Username updated successfully.',
+ variant: 'success',
+ });
+ } catch (error) {
+ setError('username', { message: error[0].reason });
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/packages/manager/src/features/Profile/LishSettings/LishSettings.tsx b/packages/manager/src/features/Profile/LishSettings/LishSettings.tsx
index 952eb980ce0..dc6f213bb8e 100644
--- a/packages/manager/src/features/Profile/LishSettings/LishSettings.tsx
+++ b/packages/manager/src/features/Profile/LishSettings/LishSettings.tsx
@@ -1,19 +1,16 @@
+import { Box, FormControl, Paper } from '@linode/ui';
import { useTheme } from '@mui/material/styles';
+import { createLazyRoute } from '@tanstack/react-router';
import { equals, lensPath, remove, set } from 'ramda';
import * as React from 'react';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
-import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { DocumentTitleSegment } from 'src/components/DocumentTitle';
-import { FormControl } from 'src/components/FormControl';
import { Notice } from 'src/components/Notice/Notice';
-import { Paper } from 'src/components/Paper';
import { TextField } from 'src/components/TextField';
import { Typography } from 'src/components/Typography';
-import { createLazyRoute } from '@tanstack/react-router';
-
import { useMutateProfile, useProfile } from 'src/queries/profile/profile';
import { getAPIErrorOrDefault } from 'src/utilities/errorUtils';
import { getAPIErrorFor } from 'src/utilities/getAPIErrorFor';
diff --git a/packages/manager/src/features/Profile/OAuthClients/CreateOAuthClientDrawer.tsx b/packages/manager/src/features/Profile/OAuthClients/CreateOAuthClientDrawer.tsx
index 36a783ae129..5b49daef10d 100644
--- a/packages/manager/src/features/Profile/OAuthClients/CreateOAuthClientDrawer.tsx
+++ b/packages/manager/src/features/Profile/OAuthClients/CreateOAuthClientDrawer.tsx
@@ -1,10 +1,10 @@
+import { FormControl } from '@linode/ui';
import { useFormik } from 'formik';
import * as React from 'react';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { Checkbox } from 'src/components/Checkbox';
import { Drawer } from 'src/components/Drawer';
-import { FormControl } from 'src/components/FormControl';
import { FormControlLabel } from 'src/components/FormControlLabel';
import { Notice } from 'src/components/Notice/Notice';
import { TextField } from 'src/components/TextField';
diff --git a/packages/manager/src/features/Profile/OAuthClients/EditOAuthClientDrawer.tsx b/packages/manager/src/features/Profile/OAuthClients/EditOAuthClientDrawer.tsx
index 67cf9149f7d..4f2d4673e55 100644
--- a/packages/manager/src/features/Profile/OAuthClients/EditOAuthClientDrawer.tsx
+++ b/packages/manager/src/features/Profile/OAuthClients/EditOAuthClientDrawer.tsx
@@ -1,10 +1,10 @@
+import { FormControl } from '@linode/ui';
import { useFormik } from 'formik';
import * as React from 'react';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { Checkbox } from 'src/components/Checkbox';
import { Drawer } from 'src/components/Drawer';
-import { FormControl } from 'src/components/FormControl';
import { FormControlLabel } from 'src/components/FormControlLabel';
import { Notice } from 'src/components/Notice/Notice';
import { TextField } from 'src/components/TextField';
diff --git a/packages/manager/src/features/Profile/OAuthClients/OAuthClients.tsx b/packages/manager/src/features/Profile/OAuthClients/OAuthClients.tsx
index b30f1e6168e..aa2840216d3 100644
--- a/packages/manager/src/features/Profile/OAuthClients/OAuthClients.tsx
+++ b/packages/manager/src/features/Profile/OAuthClients/OAuthClients.tsx
@@ -1,7 +1,7 @@
+import { Box } from '@linode/ui';
import { createLazyRoute } from '@tanstack/react-router';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { DocumentTitleSegment } from 'src/components/DocumentTitle';
import { Hidden } from 'src/components/Hidden';
diff --git a/packages/manager/src/features/Profile/Referrals/Referrals.tsx b/packages/manager/src/features/Profile/Referrals/Referrals.tsx
index 79dc5fa98c3..6ea34956725 100644
--- a/packages/manager/src/features/Profile/Referrals/Referrals.tsx
+++ b/packages/manager/src/features/Profile/Referrals/Referrals.tsx
@@ -1,3 +1,4 @@
+import { Paper } from '@linode/ui';
import Grid from '@mui/material/Unstable_Grid2';
import { createLazyRoute } from '@tanstack/react-router';
import * as React from 'react';
@@ -10,7 +11,6 @@ import { CopyableTextField } from 'src/components/CopyableTextField/CopyableText
import { DocumentTitleSegment } from 'src/components/DocumentTitle';
import { Link } from 'src/components/Link';
import { Notice } from 'src/components/Notice/Notice';
-import { Paper } from 'src/components/Paper';
import { Typography } from 'src/components/Typography';
import { useProfile } from 'src/queries/profile/profile';
import { getAPIErrorOrDefault } from 'src/utilities/errorUtils';
diff --git a/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx b/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx
index b5f225153c4..6bb1e386a0f 100644
--- a/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx
+++ b/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx
@@ -1,8 +1,8 @@
+import { Box } from '@linode/ui';
import { styled } from '@mui/material/styles';
import * as React from 'react';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
-import { Box } from 'src/components/Box';
import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog';
import { CopyableTextField } from 'src/components/CopyableTextField/CopyableTextField';
import { Notice } from 'src/components/Notice/Notice';
diff --git a/packages/manager/src/features/Profile/Settings/PreferenceEditor.tsx b/packages/manager/src/features/Profile/Settings/PreferenceEditor.tsx
index dd2fc9ac06c..c5905bb42f2 100644
--- a/packages/manager/src/features/Profile/Settings/PreferenceEditor.tsx
+++ b/packages/manager/src/features/Profile/Settings/PreferenceEditor.tsx
@@ -1,8 +1,8 @@
+import { Box } from '@linode/ui';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
-import { Dialog, DialogProps } from 'src/components/Dialog/Dialog';
+import { Dialog } from 'src/components/Dialog/Dialog';
import { Link } from 'src/components/Link';
import { Notice } from 'src/components/Notice/Notice';
import { Typography } from 'src/components/Typography';
@@ -11,6 +11,8 @@ import {
usePreferences,
} from 'src/queries/profile/preferences';
+import type { DialogProps } from 'src/components/Dialog/Dialog';
+
type Props = Pick;
export const PreferenceEditor = (props: Props) => {
diff --git a/packages/manager/src/features/Profile/Settings/Settings.tsx b/packages/manager/src/features/Profile/Settings/Settings.tsx
index 110734ff1f0..d8d9097879f 100644
--- a/packages/manager/src/features/Profile/Settings/Settings.tsx
+++ b/packages/manager/src/features/Profile/Settings/Settings.tsx
@@ -1,3 +1,4 @@
+import { Paper } from '@linode/ui';
import { createLazyRoute } from '@tanstack/react-router';
import * as React from 'react';
import { useHistory, useLocation } from 'react-router-dom';
@@ -5,7 +6,6 @@ import { useHistory, useLocation } from 'react-router-dom';
import { Code } from 'src/components/Code/Code';
import { DocumentTitleSegment } from 'src/components/DocumentTitle';
import { FormControlLabel } from 'src/components/FormControlLabel';
-import { Paper } from 'src/components/Paper';
import { Radio } from 'src/components/Radio/Radio';
import { RadioGroup } from 'src/components/RadioGroup';
import { Stack } from 'src/components/Stack';
@@ -49,7 +49,9 @@ export const ProfileSettings = () => {
preferences?.type_to_confirm === undefined ||
preferences?.type_to_confirm === true;
+ // Email notifications and masking sensitive data are disabled by default until the user explicitly enables it.
const areEmailNotificationsEnabled = profile?.email_notifications === true;
+ const isSensitiveDataMasked = preferences?.maskSensitiveData === true;
return (
@@ -118,6 +120,27 @@ export const ProfileSettings = () => {
}`}
/>
+
+
+ Mask Sensitive Data
+
+
+ Mask IP addresses and user contact information for data privacy.
+
+
+ updatePreferences({ maskSensitiveData: checked })
+ }
+ checked={isSensitiveDataMasked}
+ />
+ }
+ label={`Sensitive data is ${
+ isSensitiveDataMasked ? 'masked' : 'visible'
+ }`}
+ />
+
{
return {
diff --git a/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanel.styles.ts b/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanel.styles.ts
index f17330b1a33..bdb98656bf6 100644
--- a/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanel.styles.ts
+++ b/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanel.styles.ts
@@ -1,31 +1,39 @@
+import { Paper } from '@linode/ui';
import { styled } from '@mui/material/styles';
-import { Paper } from 'src/components/Paper';
import { Table } from 'src/components/Table';
-export const StyledLinkDiv = styled('div', { label: 'StyledLinkDiv' })(({ theme }) => ({
- display: 'block',
- marginBottom: 24,
- marginTop: theme.spacing(),
- textAlign: 'right',
-}));
+export const StyledLinkDiv = styled('div', { label: 'StyledLinkDiv' })(
+ ({ theme }) => ({
+ display: 'block',
+ marginBottom: 24,
+ marginTop: theme.spacing(),
+ textAlign: 'right',
+ })
+);
-export const StyledPanelPaper = styled(Paper, { label: 'StyledPanelPaper' })(({ theme }) => ({
- backgroundColor: theme.color.white,
- flexGrow: 1,
- marginBottom: theme.spacing(3),
- width: '100%',
-}));
+export const StyledPanelPaper = styled(Paper, { label: 'StyledPanelPaper' })(
+ ({ theme }) => ({
+ backgroundColor: theme.color.white,
+ flexGrow: 1,
+ marginBottom: theme.spacing(3),
+ width: '100%',
+ })
+);
-export const StyledSelectingPaper = styled(Paper, { label: 'StyledSelectingPaper' })({
+export const StyledSelectingPaper = styled(Paper, {
+ label: 'StyledSelectingPaper',
+})({
maxHeight: '1000px',
minHeight: '400px',
overflowY: 'scroll',
paddingTop: 0,
});
-export const StyledTable = styled(Table, { label: 'StyledTable' })(({ theme }) => ({
- backgroundColor: theme.color.white,
- flexGrow: 1,
- width: '100%',
-}));
\ No newline at end of file
+export const StyledTable = styled(Table, { label: 'StyledTable' })(
+ ({ theme }) => ({
+ backgroundColor: theme.color.white,
+ flexGrow: 1,
+ width: '100%',
+ })
+);
diff --git a/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanel.tsx b/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanel.tsx
index 007a63c7914..246c633d453 100644
--- a/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanel.tsx
+++ b/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanel.tsx
@@ -1,25 +1,14 @@
-import { Grant } from '@linode/api-v4/lib/account';
-import { Image } from '@linode/api-v4/lib/images';
-import { Linode } from '@linode/api-v4/lib/linodes';
-import {
- StackScript,
- UserDefinedField,
- getStackScript,
-} from '@linode/api-v4/lib/stackscripts';
-import { Filter, Params, ResourcePage } from '@linode/api-v4/lib/types';
+import { getStackScript } from '@linode/api-v4/lib/stackscripts';
+import { Box } from '@linode/ui';
import * as React from 'react';
import { compose } from 'recompose';
-import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { CircleProgress } from 'src/components/CircleProgress';
import { Notice } from 'src/components/Notice/Notice';
-import { RenderGuard, RenderGuardProps } from 'src/components/RenderGuard';
+import { RenderGuard } from 'src/components/RenderGuard';
import { Typography } from 'src/components/Typography';
-import {
- WithProfileProps,
- withProfile,
-} from 'src/containers/profile.container';
+import { withProfile } from 'src/containers/profile.container';
import { formatDate } from 'src/utilities/formatDate';
import { getQueryParamFromQueryString } from 'src/utilities/queryParams';
import { truncate } from 'src/utilities/truncate';
@@ -34,6 +23,17 @@ import {
import SelectStackScriptPanelContent from './SelectStackScriptPanelContent';
import StackScriptSelectionRow from './StackScriptSelectionRow';
+import type { Grant } from '@linode/api-v4/lib/account';
+import type { Image } from '@linode/api-v4/lib/images';
+import type { Linode } from '@linode/api-v4/lib/linodes';
+import type {
+ StackScript,
+ UserDefinedField,
+} from '@linode/api-v4/lib/stackscripts';
+import type { Filter, Params, ResourcePage } from '@linode/api-v4/lib/types';
+import type { RenderGuardProps } from 'src/components/RenderGuard';
+import type { WithProfileProps } from 'src/containers/profile.container';
+
export interface ExtendedLinode extends Linode {
heading: string;
subHeadings: string[];
@@ -52,6 +52,7 @@ interface Props extends RenderGuardProps {
images: string[],
userDefinedFields: UserDefinedField[]
) => void;
+ openStackScriptDetailsDialog: (stackscriptId: number) => void;
publicImages: Record;
request: (
username: string,
@@ -76,6 +77,17 @@ class SelectStackScriptPanel extends React.Component<
SelectStackScriptPanelProps,
State
> {
+ mounted: boolean = false;
+
+ resetStackScript = () => {
+ this.setState({ stackScript: undefined, stackScriptLoading: false });
+ };
+
+ state: State = {
+ stackScriptError: false,
+ stackScriptLoading: false,
+ };
+
componentDidMount() {
const selected = +getQueryParamFromQueryString(
location.search,
@@ -128,6 +140,9 @@ class SelectStackScriptPanel extends React.Component<
/>
);
}
-
- mounted: boolean = false;
-
- resetStackScript = () => {
- this.setState({ stackScript: undefined, stackScriptLoading: false });
- };
-
- state: State = {
- stackScriptError: false,
- stackScriptLoading: false,
- };
}
export default compose(
diff --git a/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanelContent.tsx b/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanelContent.tsx
index 9027398dda2..0b8c07ee7f7 100644
--- a/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanelContent.tsx
+++ b/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanelContent.tsx
@@ -21,6 +21,7 @@ interface Props {
images: string[],
userDefinedFields: UserDefinedField[]
) => void;
+ openStackScriptDetailsDialog: (stackscriptId: number) => void;
publicImages: Record;
request: StackScriptsRequest;
resetStackScriptSelection: () => void;
@@ -47,6 +48,7 @@ class SelectStackScriptPanelContent extends React.Component<
disabled={this.props.disabled}
isSorting={this.props.isSorting}
onSelect={this.handleSelectStackScript}
+ openStackScriptDetailsDialog={this.props.openStackScriptDetailsDialog}
publicImages={this.props.publicImages}
selectedId={selected}
/>
diff --git a/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptsSection.tsx b/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptsSection.tsx
index a207afdc66b..6d3501573a0 100644
--- a/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptsSection.tsx
+++ b/packages/manager/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptsSection.tsx
@@ -18,12 +18,20 @@ interface Props {
disabled?: boolean;
isSorting: boolean;
onSelect: (s: StackScript) => void;
+ openStackScriptDetailsDialog: (stackscriptId: number) => void;
publicImages: Record;
selectedId?: number;
}
export const SelectStackScriptsSection = (props: Props) => {
- const { data, disabled, isSorting, onSelect, selectedId } = props;
+ const {
+ data,
+ disabled,
+ isSorting,
+ onSelect,
+ openStackScriptDetailsDialog,
+ selectedId,
+ } = props;
const { data: profile } = useProfile();
@@ -40,6 +48,7 @@ export const SelectStackScriptsSection = (props: Props) => {
key={s.id}
label={s.label}
onSelect={() => onSelect(s)}
+ openStackScriptDetailsDialog={openStackScriptDetailsDialog}
stackScriptID={s.id}
stackScriptUsername={s.username}
updateFor={[selectedId === s.id]}
diff --git a/packages/manager/src/features/StackScripts/SelectStackScriptPanel/StackScriptSelectionRow.tsx b/packages/manager/src/features/StackScripts/SelectStackScriptPanel/StackScriptSelectionRow.tsx
index 8d253c45ca3..e62856f1015 100644
--- a/packages/manager/src/features/StackScripts/SelectStackScriptPanel/StackScriptSelectionRow.tsx
+++ b/packages/manager/src/features/StackScripts/SelectStackScriptPanel/StackScriptSelectionRow.tsx
@@ -1,13 +1,10 @@
import * as React from 'react';
-import { MapDispatchToProps, connect } from 'react-redux';
-import { compose as recompose } from 'recompose';
import { Radio } from 'src/components/Radio/Radio';
-import { RenderGuard, RenderGuardProps } from 'src/components/RenderGuard';
+import { RenderGuard } from 'src/components/RenderGuard';
import { TableCell } from 'src/components/TableCell';
import { TableRow } from 'src/components/TableRow';
import { Typography } from 'src/components/Typography';
-import { openStackScriptDialog as openStackScriptDialogAction } from 'src/store/stackScriptDialog';
import {
StyledDetailsButton,
@@ -20,7 +17,7 @@ import {
StyledUsernameLabel,
} from '../CommonStackScript.styles';
-export interface Props {
+interface Props {
checked?: boolean;
deploymentsActive: number;
description: string;
@@ -28,24 +25,13 @@ export interface Props {
disabledCheckedSelect?: boolean;
label: string;
onSelect?: (e: React.ChangeEvent, value: boolean) => void;
+ openStackScriptDetailsDialog: (stackscriptId: number) => void;
stackScriptID: number;
stackScriptUsername: string;
updated: string;
}
-interface DispatchProps {
- openStackScriptDialog: (stackScriptId: number) => void;
-}
-
-export interface StackScriptSelectionRowProps
- extends Props,
- DispatchProps,
- RenderGuardProps {}
-
-export class StackScriptSelectionRow extends React.Component<
- StackScriptSelectionRowProps,
- {}
-> {
+export class StackScriptSelectionRow extends React.Component {
render() {
const {
checked,
@@ -54,7 +40,7 @@ export class StackScriptSelectionRow extends React.Component<
disabledCheckedSelect,
label,
onSelect,
- openStackScriptDialog,
+ openStackScriptDetailsDialog,
stackScriptID,
stackScriptUsername,
} = this.props;
@@ -62,7 +48,7 @@ export class StackScriptSelectionRow extends React.Component<
const renderLabel = () => {
const openDialog = (event: React.MouseEvent) => {
event.stopPropagation();
- openStackScriptDialog(stackScriptID);
+ openStackScriptDetailsDialog(stackScriptID);
};
return (
@@ -110,18 +96,4 @@ export class StackScriptSelectionRow extends React.Component<
}
}
-const mapDispatchToProps: MapDispatchToProps = (
- dispatch
-) => {
- return {
- openStackScriptDialog: (stackScriptId: number) =>
- dispatch(openStackScriptDialogAction(stackScriptId)),
- };
-};
-
-interface ExportProps extends Props, RenderGuardProps {}
-
-export default recompose(
- connect(undefined, mapDispatchToProps),
- RenderGuard
-)(StackScriptSelectionRow);
+export default RenderGuard(StackScriptSelectionRow);
diff --git a/packages/manager/src/features/StackScripts/StackScriptBase/StackScriptBase.styles.ts b/packages/manager/src/features/StackScripts/StackScriptBase/StackScriptBase.styles.ts
index b4a596d5f8f..0a93d7cccc2 100644
--- a/packages/manager/src/features/StackScripts/StackScriptBase/StackScriptBase.styles.ts
+++ b/packages/manager/src/features/StackScripts/StackScriptBase/StackScriptBase.styles.ts
@@ -1,9 +1,9 @@
+import { omittedProps } from '@linode/ui';
import { styled } from '@mui/material/styles';
import { DebouncedSearchTextField } from 'src/components/DebouncedSearchTextField';
import { Placeholder } from 'src/components/Placeholder/Placeholder';
import { Table } from 'src/components/Table';
-import { omittedProps } from 'src/utilities/omittedProps';
import type { WithStackScriptBaseOptions } from './StackScriptBase';
@@ -55,7 +55,7 @@ export const StyledDebouncedSearchTextfield = styled(DebouncedSearchTextField, {
'& .input': {
backgroundColor: theme.bg.bgPaper,
border: `1px solid ${theme.color.grey3}`,
- borderRadius: 0,
+ borderRadius: theme.tokens.borderRadius.None,
minHeight: 'auto',
minWidth: 415,
},
diff --git a/packages/manager/src/features/StackScripts/StackScriptBase/StackScriptsEmptyLandingPage.tsx b/packages/manager/src/features/StackScripts/StackScriptBase/StackScriptsEmptyLandingPage.tsx
index 55bf8070ad9..b7145c0ea10 100644
--- a/packages/manager/src/features/StackScripts/StackScriptBase/StackScriptsEmptyLandingPage.tsx
+++ b/packages/manager/src/features/StackScripts/StackScriptBase/StackScriptsEmptyLandingPage.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
-import StackScriptsIcon from 'src/assets/icons/entityIcons/stackscript.svg';
+import LinodeIcon from 'src/assets/icons/entityIcons/linode.svg';
import { ResourcesSection } from 'src/components/EmptyLandingPageResources/ResourcesSection';
import { sendEvent } from 'src/utilities/analytics/utils';
@@ -35,7 +35,7 @@ export const StackScriptsEmptyLandingState = (props: Props) => {
]}
gettingStartedGuidesData={gettingStartedGuides}
headers={headers}
- icon={StackScriptsIcon}
+ icon={LinodeIcon}
linkAnalyticsEvent={linkAnalyticsEvent}
youtubeLinkData={youtubeLinkData}
/>
diff --git a/packages/manager/src/features/StackScripts/StackScriptCreate/StackScriptCreate.test.tsx b/packages/manager/src/features/StackScripts/StackScriptCreate/StackScriptCreate.test.tsx
index c1978cd7c30..49012e7b97b 100644
--- a/packages/manager/src/features/StackScripts/StackScriptCreate/StackScriptCreate.test.tsx
+++ b/packages/manager/src/features/StackScripts/StackScriptCreate/StackScriptCreate.test.tsx
@@ -1,9 +1,9 @@
import * as React from 'react';
import { reactRouterProps } from 'src/__data__/reactRouterProps';
-import { imageFactory, normalizeEntities, profileFactory } from 'src/factories';
+import { profileFactory } from 'src/factories';
import { queryClientFactory } from 'src/queries/base';
-import { renderWithTheme } from 'src/utilities/testHelpers';
+import { renderWithThemeAndHookFormContext } from 'src/utilities/testHelpers';
import { StackScriptCreate } from './StackScriptCreate';
@@ -11,29 +11,26 @@ import type { Grants, Profile } from '@linode/api-v4/lib';
import type { APIError } from '@linode/api-v4/lib/types';
import type { UseQueryResult } from '@tanstack/react-query';
-const images = normalizeEntities(imageFactory.buildList(10));
const queryClient = queryClientFactory();
describe('StackScriptCreate', () => {
it('should render header, inputs, and buttons', () => {
- const { getByLabelText, getByText } = renderWithTheme(
-
- }
- grants={{ data: {} } as UseQueryResult}
- imagesData={images}
- imagesLastUpdated={0}
- imagesLoading={false}
- mode="create"
- queryClient={queryClient}
- />,
- { queryClient }
- );
+ const { getByLabelText, getByText } = renderWithThemeAndHookFormContext({
+ component: (
+
+ }
+ grants={{ data: {} } as UseQueryResult}
+ mode="create"
+ queryClient={queryClient}
+ />
+ ),
+ });
expect(getByText('Create')).toBeVisible();
diff --git a/packages/manager/src/features/StackScripts/StackScriptCreate/StackScriptCreate.tsx b/packages/manager/src/features/StackScripts/StackScriptCreate/StackScriptCreate.tsx
index b28cc58edbd..0d232b42e56 100644
--- a/packages/manager/src/features/StackScripts/StackScriptCreate/StackScriptCreate.tsx
+++ b/packages/manager/src/features/StackScripts/StackScriptCreate/StackScriptCreate.tsx
@@ -17,12 +17,10 @@ import { ErrorState } from 'src/components/ErrorState/ErrorState';
import { LandingHeader } from 'src/components/LandingHeader';
import { Notice } from 'src/components/Notice/Notice';
import { Typography } from 'src/components/Typography';
-import { withImages } from 'src/containers/images.container';
import { withProfile } from 'src/containers/profile.container';
import { withQueryClient } from 'src/containers/withQueryClient.container';
import { StackScriptForm } from 'src/features/StackScripts/StackScriptForm/StackScriptForm';
import { profileQueries } from 'src/queries/profile/profile';
-import { filterImagesByType } from 'src/store/image/image.helpers';
import { getAPIErrorFor } from 'src/utilities/getAPIErrorFor';
import { scrollErrorIntoView } from 'src/utilities/scrollErrorIntoView';
import { storage } from 'src/utilities/storage';
@@ -36,7 +34,6 @@ import type {
import type { Account, Grant } from '@linode/api-v4/lib/account';
import type { QueryClient } from '@tanstack/react-query';
import type { RouteComponentProps } from 'react-router-dom';
-import type { WithImagesProps } from 'src/containers/images.container';
import type { WithProfileProps } from 'src/containers/profile.container';
import type { WithQueryClientProps } from 'src/containers/withQueryClient.container';
@@ -59,7 +56,6 @@ interface Props {
}
type CombinedProps = Props &
- WithImagesProps &
WithProfileProps &
RouteComponentProps<{ stackScriptID: string }> &
WithQueryClientProps;
@@ -409,7 +405,6 @@ export class StackScriptCreate extends React.Component {
render() {
const {
grants,
- imagesData,
location,
match: {
params: { stackScriptID },
@@ -429,17 +424,11 @@ export class StackScriptCreate extends React.Component {
// apiResponse
} = this.state;
- const _imagesData = filterImagesByType(imagesData, 'public');
-
const hasErrorFor = getAPIErrorFor(errorResources, errors);
const generalError = hasErrorFor('none');
const hasUnsavedChanges = this.hasUnsavedChanges();
- const availableImages = Object.values(_imagesData).filter(
- (thisImage) => !thisImage.label.match(/kube/i)
- );
-
const stackScriptGrants = grants.data?.stackscript;
const grantsForThisStackScript = stackScriptGrants?.find(
@@ -503,10 +492,6 @@ export class StackScriptCreate extends React.Component {
handler: this.handleDescriptionChange,
value: description,
}}
- images={{
- available: availableImages,
- selected: images,
- }}
label={{
handler: this.handleLabelChange,
value: label,
@@ -528,6 +513,7 @@ export class StackScriptCreate extends React.Component {
onCancel={this.handleOpenDialog}
onSelectChange={this.handleChooseImage}
onSubmit={this.handleSubmit}
+ selectedImages={images}
/>
{this.renderCancelStackScriptDialog()}
@@ -536,7 +522,6 @@ export class StackScriptCreate extends React.Component {
}
const enhanced = compose(
- withImages,
withRouter,
withProfile,
withQueryClient
diff --git a/packages/manager/src/features/StackScripts/StackScriptDialog.tsx b/packages/manager/src/features/StackScripts/StackScriptDialog.tsx
deleted file mode 100644
index 56f1509cba5..00000000000
--- a/packages/manager/src/features/StackScripts/StackScriptDialog.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { StackScript, getStackScript } from '@linode/api-v4/lib/stackscripts';
-import { path, pathOr } from 'ramda';
-import * as React from 'react';
-import { MapDispatchToProps, connect } from 'react-redux';
-
-import { CircleProgress } from 'src/components/CircleProgress';
-import { Dialog } from 'src/components/Dialog/Dialog';
-import { Notice } from 'src/components/Notice/Notice';
-import { StackScript as _StackScript } from 'src/components/StackScript/StackScript';
-import { ApplicationState } from 'src/store';
-import { closeStackScriptDialog } from 'src/store/stackScriptDialog';
-import { MapState } from 'src/store/types';
-
-interface DispatchProps {
- closeDrawer: () => void;
-}
-
-interface Props {
- open: boolean;
- stackScriptId?: number;
-}
-
-interface StackScriptDialogProps extends DispatchProps, Props {}
-
-export const StackScriptDialog = (props: StackScriptDialogProps) => {
- const { closeDrawer, open, stackScriptId } = props;
-
- const [stackScript, setStackScript] = React.useState(
- undefined
- );
- const [loading, setLoading] = React.useState(false);
- const [error, setError] = React.useState(true);
-
- const title = stackScript
- ? `${stackScript.username} / ${stackScript.label}`
- : 'StackScript';
-
- React.useEffect(() => {
- if (stackScriptId) {
- setLoading(true);
- getStackScript(stackScriptId)
- .then((stackScript) => {
- setStackScript(stackScript);
- setLoading(false);
- setError(false);
- })
- .catch(() => {
- setLoading(false);
- setError(true);
- });
- }
- }, [stackScriptId]);
-
- return (
-
- );
-};
-
-const mapDispatchToProps: MapDispatchToProps = (
- dispatch
-) => {
- return {
- closeDrawer: () => dispatch(closeStackScriptDialog()),
- };
-};
-
-const mapStateToProps: MapState = (state: ApplicationState) => ({
- open: pathOr(false, ['stackScriptDialog', 'open'], state),
- stackScriptId: path(['stackScriptDialog', 'stackScriptId'], state),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(StackScriptDialog);
diff --git a/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.test.tsx b/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.test.tsx
index bc7a4574db5..b4be7d1e3eb 100644
--- a/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.test.tsx
+++ b/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.test.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
-import { renderWithTheme } from 'src/utilities/testHelpers';
+import { renderWithThemeAndHookFormContext } from 'src/utilities/testHelpers';
import { StackScriptForm } from './StackScriptForm';
@@ -12,10 +12,6 @@ const props = {
},
disableSubmit: false,
errors: [],
- images: {
- available: [],
- selected: [],
- },
isSubmitting: false,
label: {
handler: vi.fn(),
@@ -33,11 +29,14 @@ const props = {
handler: vi.fn(),
value: '',
},
+ selectedImages: [],
};
describe('StackScriptCreate', () => {
it('should render', () => {
- const { getByText } = renderWithTheme( );
+ const { getByText } = renderWithThemeAndHookFormContext({
+ component: ,
+ });
getByText(/stackscript label/i);
});
});
diff --git a/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.tsx b/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.tsx
index 5a736e413ec..41bab8c59ed 100644
--- a/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.tsx
+++ b/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.tsx
@@ -1,11 +1,10 @@
+import { InputAdornment, Paper } from '@linode/ui';
import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';
-import { InputAdornment } from 'src/components/InputAdornment';
-import { Paper } from 'src/components/Paper';
+import { ImageSelect } from 'src/components/ImageSelect/ImageSelect';
import { TextField } from 'src/components/TextField';
import { Typography } from 'src/components/Typography';
-import { ImageSelect } from 'src/features/Images/ImageSelect';
import { getAPIErrorFor } from 'src/utilities/getAPIErrorFor';
import {
@@ -23,20 +22,12 @@ interface TextFieldHandler {
value: string;
}
-interface Images {
- // available to select in the dropdown
- available: Image[];
- // image ids that are already selected
- selected: string[];
-}
-
interface Props {
currentUser: string;
description: TextFieldHandler;
disableSubmit: boolean;
disabled?: boolean;
errors?: APIError[];
- images: Images;
isSubmitting: boolean;
label: TextFieldHandler;
mode: 'create' | 'edit';
@@ -45,6 +36,7 @@ interface Props {
onSubmit: () => void;
revision: TextFieldHandler;
script: TextFieldHandler;
+ selectedImages: string[];
}
const errorResources = {
@@ -60,7 +52,6 @@ export const StackScriptForm = React.memo((props: Props) => {
disableSubmit,
disabled,
errors,
- images,
isSubmitting,
label,
mode,
@@ -69,6 +60,7 @@ export const StackScriptForm = React.memo((props: Props) => {
onSubmit,
revision,
script,
+ selectedImages,
} = props;
const hasErrorFor = getAPIErrorFor(errorResources, errors);
@@ -104,19 +96,21 @@ export const StackScriptForm = React.memo((props: Props) => {
value={description.value}
/>
diff --git a/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/AppInfo.tsx b/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/AppInfo.tsx
index cbe13db862b..116175721e6 100644
--- a/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/AppInfo.tsx
+++ b/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/AppInfo.tsx
@@ -1,7 +1,7 @@
+import { IconButton } from '@linode/ui';
import * as React from 'react';
import Info from 'src/assets/icons/info.svg';
-import { IconButton } from 'src/components/IconButton';
interface Props {
onClick: () => void;
diff --git a/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/FieldTypes/UserDefinedSelect.tsx b/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/FieldTypes/UserDefinedSelect.tsx
index 779e1309cde..2d911c2ae12 100644
--- a/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/FieldTypes/UserDefinedSelect.tsx
+++ b/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/FieldTypes/UserDefinedSelect.tsx
@@ -1,13 +1,14 @@
-import { UserDefinedField } from '@linode/api-v4/lib/stackscripts';
+import { InputLabel } from '@linode/ui';
import { styled } from '@mui/material/styles';
import * as React from 'react';
import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
import { FormControlLabel } from 'src/components/FormControlLabel';
-import { InputLabel } from 'src/components/InputLabel';
import { Notice } from 'src/components/Notice/Notice';
import { Radio } from 'src/components/Radio/Radio';
+import type { UserDefinedField } from '@linode/api-v4/lib/stackscripts';
+
interface Props {
error?: string;
field: UserDefinedField;
diff --git a/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/FieldTypes/UserDefinedText.tsx b/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/FieldTypes/UserDefinedText.tsx
index 61d1a9325c9..607a946b355 100644
--- a/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/FieldTypes/UserDefinedText.tsx
+++ b/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/FieldTypes/UserDefinedText.tsx
@@ -1,11 +1,12 @@
-import { UserDefinedField } from '@linode/api-v4/lib/stackscripts';
+import { omittedProps } from '@linode/ui';
import { styled } from '@mui/material/styles';
import * as React from 'react';
import { AccessPanel } from 'src/components/AccessPanel/AccessPanel';
import { RenderGuard } from 'src/components/RenderGuard';
import { TextField } from 'src/components/TextField';
-import { omittedProps } from 'src/utilities/omittedProps';
+
+import type { UserDefinedField } from '@linode/api-v4/lib/stackscripts';
interface Props {
error?: string;
@@ -19,16 +20,6 @@ interface Props {
}
class UserDefinedText extends React.Component {
- render() {
- return (
-
- {this.props.isPassword
- ? this.renderPasswordField()
- : this.renderTextField()}
-
- );
- }
-
handleUpdatePassword = (value: string) => {
const { field, updateFormState } = this.props;
updateFormState(field.name, value);
@@ -71,6 +62,16 @@ class UserDefinedText extends React.Component {
/>
);
};
+
+ render() {
+ return (
+
+ {this.props.isPassword
+ ? this.renderPasswordField()
+ : this.renderTextField()}
+
+ );
+ }
}
type StyledAccessPanelProps = Pick;
diff --git a/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/UserDefinedFieldsPanel.styles.ts b/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/UserDefinedFieldsPanel.styles.ts
index 111d44794ae..6b14c1c29a4 100644
--- a/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/UserDefinedFieldsPanel.styles.ts
+++ b/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/UserDefinedFieldsPanel.styles.ts
@@ -1,9 +1,6 @@
+import { Box, Paper, omittedProps } from '@linode/ui';
import { styled } from '@mui/material/styles';
-import { Box } from 'src/components/Box';
-import { Paper } from 'src/components/Paper';
-import { omittedProps } from 'src/utilities/omittedProps';
-
export const StyledBox = styled(Box, { label: 'StyledBox' })(({ theme }) => ({
'& > img': {
height: 60,
diff --git a/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/UserDefinedFieldsPanel.tsx b/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/UserDefinedFieldsPanel.tsx
index 7888367a821..a87a4e5a2e7 100644
--- a/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/UserDefinedFieldsPanel.tsx
+++ b/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/UserDefinedFieldsPanel.tsx
@@ -1,8 +1,8 @@
+import { Box } from '@linode/ui';
import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';
import { Link } from 'react-router-dom';
-import { Box } from 'src/components/Box';
import { Divider } from 'src/components/Divider';
import { Notice } from 'src/components/Notice/Notice';
import { RenderGuard } from 'src/components/RenderGuard';
diff --git a/packages/manager/src/features/Support/AttachFileListItem.tsx b/packages/manager/src/features/Support/AttachFileListItem.tsx
index 26daa9d028a..58b26d7bf3d 100644
--- a/packages/manager/src/features/Support/AttachFileListItem.tsx
+++ b/packages/manager/src/features/Support/AttachFileListItem.tsx
@@ -1,15 +1,15 @@
+import { InputAdornment } from '@linode/ui';
import Close from '@mui/icons-material/Close';
import CloudUpload from '@mui/icons-material/CloudUpload';
import Grid from '@mui/material/Unstable_Grid2';
-import { Theme } from '@mui/material/styles';
-import { makeStyles } from 'tss-react/mui';
import * as React from 'react';
+import { makeStyles } from 'tss-react/mui';
-import { InputAdornment } from 'src/components/InputAdornment';
import { LinearProgress } from 'src/components/LinearProgress';
import { TextField } from 'src/components/TextField';
-import { FileAttachment } from './index';
+import type { FileAttachment } from './index';
+import type { Theme } from '@mui/material/styles';
const useStyles = makeStyles()((theme: Theme) => ({
attachmentField: {
diff --git a/packages/manager/src/features/Support/Hively.tsx b/packages/manager/src/features/Support/Hively.tsx
index 2ef3716a197..9c146e8649a 100644
--- a/packages/manager/src/features/Support/Hively.tsx
+++ b/packages/manager/src/features/Support/Hively.tsx
@@ -1,14 +1,14 @@
-import { Stack } from 'src/components/Stack';
+import { Box } from '@linode/ui';
import { DateTime } from 'luxon';
import * as React from 'react';
import { Divider } from 'src/components/Divider';
import { Link } from 'src/components/Link';
+import { Stack } from 'src/components/Stack';
import { Typography } from 'src/components/Typography';
import { parseAPIDate } from 'src/utilities/date';
import { OFFICIAL_USERNAMES } from './ticketUtils';
-import { Box } from 'src/components/Box';
interface Props {
/** The username of the Linode user who created the ticket. */
diff --git a/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/MarkdownReference.tsx b/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/MarkdownReference.tsx
index e0c27ecc042..51c289edd7e 100644
--- a/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/MarkdownReference.tsx
+++ b/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/MarkdownReference.tsx
@@ -1,66 +1,56 @@
+import { Paper } from '@linode/ui';
import * as React from 'react';
-import { makeStyles } from 'tss-react/mui';
+import { HighlightedMarkdown } from 'src/components/HighlightedMarkdown/HighlightedMarkdown';
import { Link } from 'src/components/Link';
+import { Stack } from 'src/components/Stack';
import { Typography } from 'src/components/Typography';
-import type { Theme } from '@mui/material/styles';
-
-const useStyles = makeStyles()((theme: Theme) => ({
- example: {
- backgroundColor: theme.name === 'dark' ? theme.bg.white : theme.bg.offWhite,
- margin: `${theme.spacing(2)} 0`,
- padding: theme.spacing(2),
- },
- header: {
- marginBottom: theme.spacing(),
- marginTop: theme.spacing(2),
- },
-}));
-
interface Props {
isReply?: boolean;
- rootClass?: string;
}
export const MarkdownReference = (props: Props) => {
- const { classes } = useStyles();
-
return (
-
+
You can use Markdown to format your{' '}
- {props.isReply ? 'reply' : 'question'}. For more examples see this{' '}
+ {props.isReply ? 'reply' : 'question'}. For more examples, see this{' '}
Markdown cheatsheet
-
- Examples
-
-
+ theme.font.bold}>Examples
+ ({
+ backgroundColor: theme.palette.background.default,
+ p: 2,
+ })}
+ >
[I am a link](https://google.com)
-
I am a link',
}}
/>
-
-
+
+ ({
+ backgroundColor: theme.palette.background.default,
+ p: 2,
+ })}
+ >
- ```
+ ```js
const someCode = 'hello world';
```
- const someCode = "hello world"`,
- }}
+
-
-
+
+
);
};
diff --git a/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/PreviewReply.tsx b/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/PreviewReply.tsx
index 8fb97b8ceda..4c526d57a40 100644
--- a/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/PreviewReply.tsx
+++ b/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/PreviewReply.tsx
@@ -1,7 +1,7 @@
+import { Paper } from '@linode/ui';
import * as React from 'react';
import { HighlightedMarkdown } from 'src/components/HighlightedMarkdown/HighlightedMarkdown';
-import { Paper } from 'src/components/Paper';
interface Props {
error?: string;
@@ -14,12 +14,12 @@ export const PreviewReply = (props: Props) => {
return (
diff --git a/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/ReplyContainer.tsx b/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/ReplyContainer.tsx
index 6752630d1c4..383ed47eeb4 100644
--- a/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/ReplyContainer.tsx
+++ b/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/ReplyContainer.tsx
@@ -1,11 +1,9 @@
-import { SupportReply, uploadAttachment } from '@linode/api-v4/lib/support';
-import { APIError } from '@linode/api-v4/lib/types';
+import { uploadAttachment } from '@linode/api-v4';
import Grid from '@mui/material/Unstable_Grid2';
-import { Theme } from '@mui/material/styles';
-import { makeStyles } from 'tss-react/mui';
import { lensPath, set } from 'ramda';
import * as React from 'react';
import { debounce } from 'throttle-debounce';
+import { makeStyles } from 'tss-react/mui';
import { Accordion } from 'src/components/Accordion';
import { Notice } from 'src/components/Notice/Notice';
@@ -14,37 +12,19 @@ import { getAPIErrorOrDefault, getErrorMap } from 'src/utilities/errorUtils';
import { storage } from 'src/utilities/storage';
import { AttachFileForm } from '../../AttachFileForm';
-import { FileAttachment } from '../../index';
import { MarkdownReference } from './MarkdownReference';
import { ReplyActions } from './ReplyActions';
import { TabbedReply } from './TabbedReply';
+import type { FileAttachment } from '../../index';
+import type { APIError, SupportReply } from '@linode/api-v4';
+import type { Theme } from '@mui/material/styles';
+
const useStyles = makeStyles()((theme: Theme) => ({
- expPanelSummary: {
- backgroundColor: theme.name === 'dark' ? theme.bg.main : theme.bg.white,
- borderTop: `1px solid ${theme.bg.main}`,
- paddingTop: theme.spacing(),
- },
- reference: {
- [theme.breakpoints.down('sm')]: {
- padding: `${theme.spacing(2)} !important`,
- },
- [theme.breakpoints.up('sm')]: {
- marginLeft: 4,
- marginRight: 4,
- marginTop: theme.spacing(7),
- padding: `0 !important`,
- },
- },
- referenceRoot: {
- '& > p': {
- marginBottom: theme.spacing(),
- },
- },
replyContainer: {
paddingLeft: theme.spacing(6),
[theme.breakpoints.down('sm')]: {
- paddingLeft: theme.spacing(6),
+ paddingLeft: theme.spacing(5),
},
},
}));
@@ -172,23 +152,15 @@ export const ReplyContainer = (props: Props) => {
variant="error"
/>
)}
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/TabbedReply.tsx b/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/TabbedReply.tsx
index 2b0c91931cb..5a309481537 100644
--- a/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/TabbedReply.tsx
+++ b/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/TabbedReply.tsx
@@ -1,58 +1,40 @@
-import { Theme } from '@mui/material/styles';
-import { makeStyles } from 'tss-react/mui';
-import * as React from 'react';
+import React from 'react';
-import { Tab, TabbedPanel } from 'src/components/TabbedPanel/TabbedPanel';
+import { SafeTabPanel } from 'src/components/Tabs/SafeTabPanel';
+import { Tab } from 'src/components/Tabs/Tab';
+import { TabList } from 'src/components/Tabs/TabList';
+import { TabPanels } from 'src/components/Tabs/TabPanels';
+import { Tabs } from 'src/components/Tabs/Tabs';
import { PreviewReply } from './PreviewReply';
-import { Props as ReplyProps, TicketReply } from './TicketReply';
+import { TicketReply } from './TicketReply';
+
+import type { Props as ReplyProps } from './TicketReply';
interface Props extends ReplyProps {
- innerClass?: string;
isReply?: boolean;
required?: boolean;
- rootClass?: string;
}
-const useStyles = makeStyles()((theme: Theme) => ({
- root: {
- '& div[role="tablist"]': {
- marginBottom: theme.spacing(),
- marginTop: theme.spacing(),
- },
- backgroundColor: 'transparent',
- padding: 0,
- },
-}));
-
export const TabbedReply = (props: Props) => {
- const { classes } = useStyles();
- const { error, innerClass, rootClass, value, ...rest } = props;
+ const { error, value, ...rest } = props;
const title = props.isReply ? 'Reply' : 'Description';
- const tabs: Tab[] = [
- {
- render: () => {
- return ;
- },
- title: props.required ? `${title} (required)` : title,
- },
- {
- render: () => {
- return ;
- },
- title: 'Preview',
- },
- ];
-
return (
-
+
+
+ {props.required ? `${title} (required)` : title}
+ Preview
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/packages/manager/src/features/Support/SupportTicketDetail/TicketStatus.tsx b/packages/manager/src/features/Support/SupportTicketDetail/TicketStatus.tsx
index 79f3d52f439..e056efd29d6 100644
--- a/packages/manager/src/features/Support/SupportTicketDetail/TicketStatus.tsx
+++ b/packages/manager/src/features/Support/SupportTicketDetail/TicketStatus.tsx
@@ -1,11 +1,10 @@
-import { SupportTicket } from '@linode/api-v4/lib/support/types';
+import { Paper } from '@linode/ui';
import { styled } from '@mui/material/styles';
import Grid from '@mui/material/Unstable_Grid2';
import React from 'react';
import { Hidden } from 'src/components/Hidden';
import { Link } from 'src/components/Link';
-import { Paper } from 'src/components/Paper';
import { Stack } from 'src/components/Stack';
import { StatusIcon } from 'src/components/StatusIcon/StatusIcon';
import { Typography } from 'src/components/Typography';
@@ -16,6 +15,8 @@ import { getLinkTargets } from 'src/utilities/getEventsActionLink';
import { SeverityChip } from './SeverityChip';
+import type { SupportTicket } from '@linode/api-v4/lib/support/types';
+
type Props = Pick<
SupportTicket,
'entity' | 'severity' | 'status' | 'updated' | 'updated_by'
diff --git a/packages/manager/src/features/Support/SupportTickets/SupportTicketDialog.tsx b/packages/manager/src/features/Support/SupportTickets/SupportTicketDialog.tsx
index b401dd0c7ec..066ac25045c 100644
--- a/packages/manager/src/features/Support/SupportTickets/SupportTicketDialog.tsx
+++ b/packages/manager/src/features/Support/SupportTickets/SupportTicketDialog.tsx
@@ -1,11 +1,11 @@
import { yupResolver } from '@hookform/resolvers/yup';
import { uploadAttachment } from '@linode/api-v4/lib/support';
+import { Box } from '@linode/ui';
import { update } from 'ramda';
import * as React from 'react';
import { Controller, FormProvider, useForm } from 'react-hook-form';
import { useLocation } from 'react-router-dom';
import { debounce } from 'throttle-debounce';
-import { makeStyles } from 'tss-react/mui';
import { Accordion } from 'src/components/Accordion';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
@@ -44,29 +44,8 @@ import type { SMTPCustomFields } from './SupportTicketSMTPFields';
import type { CreateKubeClusterPayload } from '@linode/api-v4';
import type { TicketSeverity } from '@linode/api-v4/lib/support';
import type { CreateLinodeRequest } from '@linode/api-v4/src/linodes/types';
-import type { Theme } from '@mui/material/styles';
import type { EntityForTicketDetails } from 'src/components/SupportLink/SupportLink';
-const useStyles = makeStyles()((theme: Theme) => ({
- expPanelSummary: {
- backgroundColor: theme.name === 'dark' ? theme.bg.main : theme.bg.white,
- borderTop: `1px solid ${theme.bg.main}`,
- paddingTop: theme.spacing(1),
- },
- innerReply: {
- '& div[role="tablist"]': {
- marginBottom: theme.spacing(),
- marginTop: theme.spacing(),
- },
- padding: 0,
- },
- rootReply: {
- marginBottom: theme.spacing(2),
- marginTop: theme.spacing(2),
- padding: 0,
- },
-}));
-
interface Accumulator {
errors: AttachmentError[];
success: string[];
@@ -212,8 +191,6 @@ export const SupportTicketDialog = (props: SupportTicketDialogProps) => {
const [submitting, setSubmitting] = React.useState(false);
- const { classes } = useStyles();
-
React.useEffect(() => {
if (!open) {
resetDrawer();
@@ -490,26 +467,28 @@ export const SupportTicketDialog = (props: SupportTicketDialogProps) => {
{props.hideProductSelection ? null : (
)}
- (
-
- )}
- control={form.control}
- name="description"
- />
+
+ (
+
+ )}
+ control={form.control}
+ name="description"
+ />
+
({ mt: `${theme.spacing(0.5)} !important` })} // forcefully disable margin when accordion is expanded
>
diff --git a/packages/manager/src/features/Support/SupportTickets/SupportTicketProductSelectionFields.tsx b/packages/manager/src/features/Support/SupportTickets/SupportTicketProductSelectionFields.tsx
index 1998e626fc5..65f55f44187 100644
--- a/packages/manager/src/features/Support/SupportTickets/SupportTicketProductSelectionFields.tsx
+++ b/packages/manager/src/features/Support/SupportTickets/SupportTicketProductSelectionFields.tsx
@@ -1,8 +1,8 @@
+import { FormHelperText } from '@linode/ui';
import React from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
-import { FormHelperText } from 'src/components/FormHelperText';
import { TextField } from 'src/components/TextField';
import { useAllDatabasesQuery } from 'src/queries/databases/databases';
import { useAllDomainsQuery } from 'src/queries/domains';
diff --git a/packages/manager/src/features/Support/TicketAttachmentRow.tsx b/packages/manager/src/features/Support/TicketAttachmentRow.tsx
index 8f148dd93b9..eb978e15480 100644
--- a/packages/manager/src/features/Support/TicketAttachmentRow.tsx
+++ b/packages/manager/src/features/Support/TicketAttachmentRow.tsx
@@ -1,8 +1,7 @@
+import { Box, Paper } from '@linode/ui';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { Divider } from 'src/components/Divider';
-import { Paper } from 'src/components/Paper';
import { Stack } from 'src/components/Stack';
import { Typography } from 'src/components/Typography';
diff --git a/packages/manager/src/features/Support/TicketDetailText.tsx b/packages/manager/src/features/Support/TicketDetailText.tsx
index a346e5fa356..15aa899a8ca 100644
--- a/packages/manager/src/features/Support/TicketDetailText.tsx
+++ b/packages/manager/src/features/Support/TicketDetailText.tsx
@@ -1,13 +1,14 @@
+import { IconButton } from '@linode/ui';
import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
-import { Theme } from '@mui/material/styles';
import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';
import { makeStyles } from 'tss-react/mui';
import { HighlightedMarkdown } from 'src/components/HighlightedMarkdown/HighlightedMarkdown';
-import { IconButton } from 'src/components/IconButton';
import { truncate } from 'src/utilities/truncate';
+import type { Theme } from '@mui/material/styles';
+
const useStyles = makeStyles()((theme: Theme) => ({
expButton: {
'& svg': {
diff --git a/packages/manager/src/features/TopMenu/NotificationMenu/NotificationMenu.tsx b/packages/manager/src/features/TopMenu/NotificationMenu/NotificationMenu.tsx
index f3f8f2dfe22..195bc0d7b65 100644
--- a/packages/manager/src/features/TopMenu/NotificationMenu/NotificationMenu.tsx
+++ b/packages/manager/src/features/TopMenu/NotificationMenu/NotificationMenu.tsx
@@ -1,3 +1,4 @@
+import { Box } from '@linode/ui';
import AutorenewIcon from '@mui/icons-material/Autorenew';
import { IconButton } from '@mui/material';
import Popover from '@mui/material/Popover';
@@ -6,7 +7,6 @@ import * as React from 'react';
import { useHistory } from 'react-router-dom';
import Bell from 'src/assets/icons/notification.svg';
-import { Box } from 'src/components/Box';
import { Chip } from 'src/components/Chip';
import { Divider } from 'src/components/Divider';
import { LinkButton } from 'src/components/LinkButton';
diff --git a/packages/manager/src/features/TopMenu/SearchBar/SearchBar.styles.ts b/packages/manager/src/features/TopMenu/SearchBar/SearchBar.styles.ts
index ce2713f1cf1..f23d19daff8 100644
--- a/packages/manager/src/features/TopMenu/SearchBar/SearchBar.styles.ts
+++ b/packages/manager/src/features/TopMenu/SearchBar/SearchBar.styles.ts
@@ -1,7 +1,6 @@
+import { IconButton } from '@linode/ui';
import { styled } from '@mui/material/styles';
-import { IconButton } from 'src/components/IconButton';
-
export const StyledIconButton = styled(IconButton, {
label: 'StyledIconButton',
})(({ theme }) => ({
diff --git a/packages/manager/src/features/TopMenu/SearchBar/SearchSuggestion.styles.ts b/packages/manager/src/features/TopMenu/SearchBar/SearchSuggestion.styles.ts
index ae369de459a..ec87141ff35 100644
--- a/packages/manager/src/features/TopMenu/SearchBar/SearchSuggestion.styles.ts
+++ b/packages/manager/src/features/TopMenu/SearchBar/SearchSuggestion.styles.ts
@@ -1,8 +1,7 @@
+import { omittedProps } from '@linode/ui';
import { styled } from '@mui/material/styles';
-import { omittedProps } from 'src/utilities/omittedProps';
-
-import { SearchSuggestionProps } from './SearchSuggestion';
+import type { SearchSuggestionProps } from './SearchSuggestion';
export const StyledWrapperDiv = styled('div', {
label: 'StyledWrapperDiv',
@@ -24,7 +23,7 @@ export const StyledWrapperDiv = styled('div', {
'& .tag': {
'&:hover': {
backgroundColor: theme.palette.primary.main,
- color: 'white',
+ color: theme.tokens.color.Neutrals.White,
},
backgroundColor: theme.bg.lightBlue1,
color: theme.palette.text.primary,
diff --git a/packages/manager/src/features/TopMenu/SearchBar/SearchSuggestion.tsx b/packages/manager/src/features/TopMenu/SearchBar/SearchSuggestion.tsx
index 43d84821037..ac3b73b753d 100644
--- a/packages/manager/src/features/TopMenu/SearchBar/SearchSuggestion.tsx
+++ b/packages/manager/src/features/TopMenu/SearchBar/SearchSuggestion.tsx
@@ -1,9 +1,7 @@
-import { LinodeStatus } from '@linode/api-v4/lib/linodes';
+import { Box } from '@linode/ui';
import { pathOr } from 'ramda';
import * as React from 'react';
-import { OptionProps } from 'react-select';
-import { Box } from 'src/components/Box';
import { EntityIcon } from 'src/components/EntityIcon/EntityIcon';
import { Tag } from 'src/components/Tag/Tag';
import { linodeInTransition } from 'src/features/Linodes/transitions';
@@ -17,6 +15,9 @@ import {
StyledWrapperDiv,
} from './SearchSuggestion.styles';
+import type { LinodeStatus } from '@linode/api-v4/lib/linodes';
+import type { OptionProps } from 'react-select';
+
export interface SearchSuggestionT {
description: string;
history: any;
diff --git a/packages/manager/src/features/TopMenu/TopMenu.tsx b/packages/manager/src/features/TopMenu/TopMenu.tsx
index c44a4249123..00802f82ebb 100644
--- a/packages/manager/src/features/TopMenu/TopMenu.tsx
+++ b/packages/manager/src/features/TopMenu/TopMenu.tsx
@@ -1,10 +1,9 @@
+import { Box, IconButton } from '@linode/ui';
import MenuIcon from '@mui/icons-material/Menu';
import * as React from 'react';
import { AppBar } from 'src/components/AppBar';
-import { Box } from 'src/components/Box';
import { Hidden } from 'src/components/Hidden';
-import { IconButton } from 'src/components/IconButton';
import { Toolbar } from 'src/components/Toolbar';
import { Typography } from 'src/components/Typography';
import { useAuthentication } from 'src/hooks/useAuthentication';
@@ -41,7 +40,10 @@ export const TopMenu = React.memo((props: TopMenuProps) => {
{loggedInAsCustomer && (
-
+ theme.tokens.color.Neutrals.Black}
+ fontSize="1.2em"
+ >
You are logged in as customer: {username}
diff --git a/packages/manager/src/features/TopMenu/TopMenuTooltip.tsx b/packages/manager/src/features/TopMenu/TopMenuTooltip.tsx
index 35250f5cbfa..43bff6c2967 100644
--- a/packages/manager/src/features/TopMenu/TopMenuTooltip.tsx
+++ b/packages/manager/src/features/TopMenu/TopMenuTooltip.tsx
@@ -1,7 +1,6 @@
+import { Tooltip } from '@linode/ui';
import * as React from 'react';
-import { Tooltip } from 'src/components/Tooltip';
-
import type { Theme } from '@mui/material';
interface Props {
diff --git a/packages/manager/src/features/TopMenu/UserMenu/UserMenu.tsx b/packages/manager/src/features/TopMenu/UserMenu/UserMenu.tsx
index 1867fdaf3f1..6ea3b004c25 100644
--- a/packages/manager/src/features/TopMenu/UserMenu/UserMenu.tsx
+++ b/packages/manager/src/features/TopMenu/UserMenu/UserMenu.tsx
@@ -1,3 +1,4 @@
+import { Box, Tooltip } from '@linode/ui';
import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
import KeyboardArrowUp from '@mui/icons-material/KeyboardArrowUp';
import { styled, useMediaQuery } from '@mui/material';
@@ -8,13 +9,11 @@ import * as React from 'react';
import { Avatar } from 'src/components/Avatar/Avatar';
import { AvatarForProxy } from 'src/components/AvatarForProxy';
-import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { Divider } from 'src/components/Divider';
import { Hidden } from 'src/components/Hidden';
import { Link } from 'src/components/Link';
import { Stack } from 'src/components/Stack';
-import { Tooltip } from 'src/components/Tooltip';
import { Typography } from 'src/components/Typography';
import { switchAccountSessionContext } from 'src/context/switchAccountSessionContext';
import { SwitchAccountButton } from 'src/features/Account/SwitchAccountButton';
diff --git a/packages/manager/src/features/Users/UserPermissions.styles.ts b/packages/manager/src/features/Users/UserPermissions.styles.ts
index fc2a46a9b90..a4dd0c9e512 100644
--- a/packages/manager/src/features/Users/UserPermissions.styles.ts
+++ b/packages/manager/src/features/Users/UserPermissions.styles.ts
@@ -1,9 +1,9 @@
+import { Paper } from '@linode/ui';
import { styled } from '@mui/material/styles';
import Grid from '@mui/material/Unstable_Grid2';
import { CircleProgress } from 'src/components/CircleProgress';
import Select from 'src/components/EnhancedSelect/Select';
-import { Paper } from 'src/components/Paper';
export const StyledSelect = styled(Select, {
label: 'StyledSelect',
diff --git a/packages/manager/src/features/Users/UserPermissions.tsx b/packages/manager/src/features/Users/UserPermissions.tsx
index ec85b41df06..8bad43671f2 100644
--- a/packages/manager/src/features/Users/UserPermissions.tsx
+++ b/packages/manager/src/features/Users/UserPermissions.tsx
@@ -4,6 +4,7 @@ import {
updateGrants,
updateUser,
} from '@linode/api-v4/lib/account';
+import { Box } from '@linode/ui';
import { Paper } from '@mui/material';
import Grid from '@mui/material/Unstable_Grid2';
import { enqueueSnackbar } from 'notistack';
@@ -11,7 +12,6 @@ import { compose, flatten, lensPath, omit, set } from 'ramda';
import * as React from 'react';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
-import { Box } from 'src/components/Box';
import { CircleProgress } from 'src/components/CircleProgress';
import { DocumentTitleSegment } from 'src/components/DocumentTitle';
import { FormControlLabel } from 'src/components/FormControlLabel';
diff --git a/packages/manager/src/features/Users/UserPermissionsEntitySection.tsx b/packages/manager/src/features/Users/UserPermissionsEntitySection.tsx
index 7876e6cdec7..9ff26232bf7 100644
--- a/packages/manager/src/features/Users/UserPermissionsEntitySection.tsx
+++ b/packages/manager/src/features/Users/UserPermissionsEntitySection.tsx
@@ -6,10 +6,10 @@
* I'll create a tech debt ticket in jira to keep track of this issue.
*/
+import { Box } from '@linode/ui';
import { useTheme } from '@mui/material/styles';
import React from 'react';
-import { Box } from 'src/components/Box';
import { FormControlLabel } from 'src/components/FormControlLabel';
import { createDisplayPage } from 'src/components/Paginate';
import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter';
diff --git a/packages/manager/src/features/Users/UserProfile/DeleteUserPanel.tsx b/packages/manager/src/features/Users/UserProfile/DeleteUserPanel.tsx
index 4c9eac1481e..4d941453204 100644
--- a/packages/manager/src/features/Users/UserProfile/DeleteUserPanel.tsx
+++ b/packages/manager/src/features/Users/UserProfile/DeleteUserPanel.tsx
@@ -1,9 +1,8 @@
+import { Box, Paper } from '@linode/ui';
import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';
-import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
-import { Paper } from 'src/components/Paper';
import { Stack } from 'src/components/Stack';
import { Typography } from 'src/components/Typography';
import { PARENT_USER } from 'src/features/Account/constants';
diff --git a/packages/manager/src/features/Users/UserProfile/UserDetailsPanel.tsx b/packages/manager/src/features/Users/UserProfile/UserDetailsPanel.tsx
index f49b7bee65d..9236aaa831f 100644
--- a/packages/manager/src/features/Users/UserProfile/UserDetailsPanel.tsx
+++ b/packages/manager/src/features/Users/UserProfile/UserDetailsPanel.tsx
@@ -1,8 +1,9 @@
+import { Paper } from '@linode/ui';
import Grid from '@mui/material/Unstable_Grid2';
import React from 'react';
import { DateTimeDisplay } from 'src/components/DateTimeDisplay';
-import { Paper } from 'src/components/Paper';
+import { MaskableText } from 'src/components/MaskableText/MaskableText';
import { Stack } from 'src/components/Stack';
import { StatusIcon } from 'src/components/StatusIcon/StatusIcon';
import { TextTooltip } from 'src/components/TextTooltip';
@@ -18,11 +19,11 @@ export const UserDetailsPanel = ({ user }: Props) => {
const items = [
{
label: 'Username',
- value: {user.username} ,
+ value: ,
},
{
label: 'Email',
- value: {user.email} ,
+ value: ,
},
{
label: 'Account Access',
@@ -69,7 +70,12 @@ export const UserDetailsPanel = ({ user }: Props) => {
},
{
label: 'Verified Phone Number',
- value: {user.verified_phone_number ?? 'None'} ,
+ value: (
+
+ ),
},
{
label: 'SSH Keys',
diff --git a/packages/manager/src/features/Users/UserProfile/UserEmailPanel.tsx b/packages/manager/src/features/Users/UserProfile/UserEmailPanel.tsx
index a23049081bf..ae1f97874e2 100644
--- a/packages/manager/src/features/Users/UserProfile/UserEmailPanel.tsx
+++ b/packages/manager/src/features/Users/UserProfile/UserEmailPanel.tsx
@@ -1,9 +1,9 @@
+import { Paper } from '@linode/ui';
import { useSnackbar } from 'notistack';
import React from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Button } from 'src/components/Button/Button';
-import { Paper } from 'src/components/Paper';
import { TextField } from 'src/components/TextField';
import { RESTRICTED_FIELD_TOOLTIP } from 'src/features/Account/constants';
import { useMutateProfile, useProfile } from 'src/queries/profile/profile';
diff --git a/packages/manager/src/features/Users/UserProfile/UsernamePanel.tsx b/packages/manager/src/features/Users/UserProfile/UsernamePanel.tsx
index ac4f7b7a51a..4c97c42a81c 100644
--- a/packages/manager/src/features/Users/UserProfile/UsernamePanel.tsx
+++ b/packages/manager/src/features/Users/UserProfile/UsernamePanel.tsx
@@ -1,10 +1,10 @@
+import { Paper } from '@linode/ui';
import { useSnackbar } from 'notistack';
import React from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useHistory } from 'react-router-dom';
import { Button } from 'src/components/Button/Button';
-import { Paper } from 'src/components/Paper';
import { TextField } from 'src/components/TextField';
import { RESTRICTED_FIELD_TOOLTIP } from 'src/features/Account/constants';
import { useUpdateUserMutation } from 'src/queries/account/users';
diff --git a/packages/manager/src/features/Users/UserRow.tsx b/packages/manager/src/features/Users/UserRow.tsx
index 9b580c3714a..e49877489e9 100644
--- a/packages/manager/src/features/Users/UserRow.tsx
+++ b/packages/manager/src/features/Users/UserRow.tsx
@@ -1,11 +1,12 @@
+import { Box } from '@linode/ui';
import { useTheme } from '@mui/material/styles';
import React from 'react';
import { Avatar } from 'src/components/Avatar/Avatar';
-import { Box } from 'src/components/Box';
import { Chip } from 'src/components/Chip';
import { DateTimeDisplay } from 'src/components/DateTimeDisplay';
import { Hidden } from 'src/components/Hidden';
+import { MaskableText } from 'src/components/MaskableText/MaskableText';
import { Stack } from 'src/components/Stack';
import { StatusIcon } from 'src/components/StatusIcon/StatusIcon';
import { TableCell } from 'src/components/TableCell';
@@ -44,13 +45,16 @@ export const UserRow = ({ onDelete, user }: Props) => {
}
username={user.username}
/>
- {user.username}
+
{user.tfa_enabled && }
- {user.email}
+
+ {' '}
+
+
{user.restricted ? 'Limited' : 'Full'}
{showChildAccountAccessCol && (
diff --git a/packages/manager/src/features/Users/UsersLanding.tsx b/packages/manager/src/features/Users/UsersLanding.tsx
index a0298e8690f..0b758c36b04 100644
--- a/packages/manager/src/features/Users/UsersLanding.tsx
+++ b/packages/manager/src/features/Users/UsersLanding.tsx
@@ -1,8 +1,8 @@
+import { Box } from '@linode/ui';
import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { DocumentTitleSegment } from 'src/components/DocumentTitle';
import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter';
diff --git a/packages/manager/src/features/VPCs/VPCCreate/FormComponents/VPCCreateForm.styles.ts b/packages/manager/src/features/VPCs/VPCCreate/FormComponents/VPCCreateForm.styles.ts
index 183f8941b8e..2a3df71e598 100644
--- a/packages/manager/src/features/VPCs/VPCCreate/FormComponents/VPCCreateForm.styles.ts
+++ b/packages/manager/src/features/VPCs/VPCCreate/FormComponents/VPCCreateForm.styles.ts
@@ -1,7 +1,7 @@
+import { omittedProps } from '@linode/ui';
import { styled } from '@mui/material/styles';
import { Typography } from 'src/components/Typography';
-import { omittedProps } from 'src/utilities/omittedProps';
type StyledVPCFormProps = {
isDrawer?: boolean;
diff --git a/packages/manager/src/features/VPCs/VPCCreate/MultipleSubnetInput.tsx b/packages/manager/src/features/VPCs/VPCCreate/MultipleSubnetInput.tsx
index 23c3f1b42eb..7c5dc9d88e5 100644
--- a/packages/manager/src/features/VPCs/VPCCreate/MultipleSubnetInput.tsx
+++ b/packages/manager/src/features/VPCs/VPCCreate/MultipleSubnetInput.tsx
@@ -5,12 +5,13 @@ import { Button } from 'src/components/Button/Button';
import { Divider } from 'src/components/Divider';
import {
DEFAULT_SUBNET_IPV4_VALUE,
- SubnetFieldState,
getRecommendedSubnetIPv4,
} from 'src/utilities/subnets';
import { SubnetNode } from './SubnetNode';
+import type { SubnetFieldState } from 'src/utilities/subnets';
+
interface Props {
disabled?: boolean;
isDrawer?: boolean;
diff --git a/packages/manager/src/features/VPCs/VPCCreate/SubnetNode.tsx b/packages/manager/src/features/VPCs/VPCCreate/SubnetNode.tsx
index ac3eb4b7b11..b47d5fa1b12 100644
--- a/packages/manager/src/features/VPCs/VPCCreate/SubnetNode.tsx
+++ b/packages/manager/src/features/VPCs/VPCCreate/SubnetNode.tsx
@@ -1,18 +1,19 @@
+import { FormHelperText } from '@linode/ui';
import Close from '@mui/icons-material/Close';
import Stack from '@mui/material/Stack';
-import Grid from '@mui/material/Unstable_Grid2';
import { styled } from '@mui/material/styles';
+import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';
import { Button } from 'src/components/Button/Button';
-import { FormHelperText } from 'src/components/FormHelperText';
import { TextField } from 'src/components/TextField';
import {
RESERVED_IP_NUMBER,
- SubnetFieldState,
calculateAvailableIPv4sRFC1918,
} from 'src/utilities/subnets';
+import type { SubnetFieldState } from 'src/utilities/subnets';
+
interface Props {
disabled?: boolean;
// extra props enable SubnetNode to be an independent component or be part of MultipleSubnetInput
diff --git a/packages/manager/src/features/VPCs/VPCCreate/VPCCreate.tsx b/packages/manager/src/features/VPCs/VPCCreate/VPCCreate.tsx
index bfcba44b36c..8995ec53601 100644
--- a/packages/manager/src/features/VPCs/VPCCreate/VPCCreate.tsx
+++ b/packages/manager/src/features/VPCs/VPCCreate/VPCCreate.tsx
@@ -1,20 +1,20 @@
-import Grid from '@mui/material/Unstable_Grid2';
+import { Paper } from '@linode/ui';
import { styled } from '@mui/material/styles';
+import Grid from '@mui/material/Unstable_Grid2';
+import { createLazyRoute } from '@tanstack/react-router';
import * as React from 'react';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { DocumentTitleSegment } from 'src/components/DocumentTitle';
import { LandingHeader } from 'src/components/LandingHeader';
import { Notice } from 'src/components/Notice/Notice';
-import { Paper } from 'src/components/Paper';
-import { SubnetContent } from 'src/features/VPCs/VPCCreate/FormComponents/SubnetContent';
import { VPC_GETTING_STARTED_LINK } from 'src/features/VPCs/constants';
+import { SubnetContent } from 'src/features/VPCs/VPCCreate/FormComponents/SubnetContent';
import { useCreateVPC } from 'src/hooks/useCreateVPC';
import { CannotCreateVPCNotice } from './FormComponents/CannotCreateVPCNotice';
import { StyledHeaderTypography } from './FormComponents/VPCCreateForm.styles';
import { VPCTopSectionContent } from './FormComponents/VPCTopSectionContent';
-import { createLazyRoute } from '@tanstack/react-router';
const VPCCreate = () => {
const {
diff --git a/packages/manager/src/features/VPCs/VPCCreateDrawer/VPCCreateDrawer.test.tsx b/packages/manager/src/features/VPCs/VPCCreateDrawer/VPCCreateDrawer.test.tsx
index 0f86ad99e04..434decf1b5b 100644
--- a/packages/manager/src/features/VPCs/VPCCreateDrawer/VPCCreateDrawer.test.tsx
+++ b/packages/manager/src/features/VPCs/VPCCreateDrawer/VPCCreateDrawer.test.tsx
@@ -6,8 +6,8 @@ import { renderWithTheme } from 'src/utilities/testHelpers';
import { VPCCreateDrawer } from './VPCCreateDrawer';
const props = {
- handleSelectVPC: vi.fn(),
onClose: vi.fn(),
+ onSuccess: vi.fn(),
open: true,
};
diff --git a/packages/manager/src/features/VPCs/VPCCreateDrawer/VPCCreateDrawer.tsx b/packages/manager/src/features/VPCs/VPCCreateDrawer/VPCCreateDrawer.tsx
index ff58f41063a..f22abc2de96 100644
--- a/packages/manager/src/features/VPCs/VPCCreateDrawer/VPCCreateDrawer.tsx
+++ b/packages/manager/src/features/VPCs/VPCCreateDrawer/VPCCreateDrawer.tsx
@@ -1,9 +1,9 @@
+import { Box } from '@linode/ui';
import { useTheme } from '@mui/material/styles';
import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
-import { Box } from 'src/components/Box';
import { Drawer } from 'src/components/Drawer';
import { Notice } from 'src/components/Notice/Notice';
import { CannotCreateVPCNotice } from 'src/features/VPCs/VPCCreate/FormComponents/CannotCreateVPCNotice';
@@ -11,16 +11,21 @@ import { SubnetContent } from 'src/features/VPCs/VPCCreate/FormComponents/Subnet
import { VPCTopSectionContent } from 'src/features/VPCs/VPCCreate/FormComponents/VPCTopSectionContent';
import { useCreateVPC } from 'src/hooks/useCreateVPC';
+import type { VPC } from '@linode/api-v4';
+
interface Props {
- handleSelectVPC: (vpcId: number) => void;
onClose: () => void;
+ /**
+ * A function that is called when a VPC is successfully created
+ */
+ onSuccess: (vpc: VPC) => void;
open: boolean;
selectedRegion?: string;
}
export const VPCCreateDrawer = (props: Props) => {
const theme = useTheme();
- const { handleSelectVPC, onClose, open, selectedRegion } = props;
+ const { onClose, onSuccess, open, selectedRegion } = props;
const {
formik,
@@ -33,7 +38,11 @@ export const VPCCreateDrawer = (props: Props) => {
setGeneralAPIError,
setGeneralSubnetErrorsFromAPI,
userCannotAddVPC,
- } = useCreateVPC({ handleSelectVPC, onDrawerClose: onClose, selectedRegion });
+ } = useCreateVPC({
+ handleSelectVPC: onSuccess,
+ onDrawerClose: onClose,
+ selectedRegion,
+ });
const { errors, handleSubmit, resetForm, setFieldValue, values } = formik;
diff --git a/packages/manager/src/features/VPCs/VPCDetail/AssignIPRanges.tsx b/packages/manager/src/features/VPCs/VPCDetail/AssignIPRanges.tsx
index a917a31a595..8856b1c656a 100644
--- a/packages/manager/src/features/VPCs/VPCDetail/AssignIPRanges.tsx
+++ b/packages/manager/src/features/VPCs/VPCDetail/AssignIPRanges.tsx
@@ -1,7 +1,7 @@
+import { Box } from '@linode/ui';
import { styled, useTheme } from '@mui/material/styles';
import * as React from 'react';
-import { Box } from 'src/components/Box';
import { Divider } from 'src/components/Divider';
import { Link } from 'src/components/Link';
import { MultipleIPInput } from 'src/components/MultipleIPInput/MultipleIPInput';
diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.styles.ts b/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.styles.ts
index 8bb12531112..295cf905c69 100644
--- a/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.styles.ts
+++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.styles.ts
@@ -1,6 +1,6 @@
+import { Box } from '@linode/ui';
import { styled } from '@mui/material/styles';
-import { Box } from 'src/components/Box';
export const StyledButtonBox = styled(Box, { label: 'StyledButtonBox' })(
({ theme }) => ({
display: 'flex',
diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx
index 8a532231cc0..7a53ece0254 100644
--- a/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx
+++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx
@@ -1,16 +1,15 @@
import { appendConfigInterface } from '@linode/api-v4';
+import { Box, FormHelperText } from '@linode/ui';
import { useTheme } from '@mui/material/styles';
import { useFormik } from 'formik';
import * as React from 'react';
import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
-import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { Checkbox } from 'src/components/Checkbox';
import { DownloadCSV } from 'src/components/DownloadCSV/DownloadCSV';
import { Drawer } from 'src/components/Drawer';
import { FormControlLabel } from 'src/components/FormControlLabel';
-import { FormHelperText } from 'src/components/FormHelperText';
import { Link } from 'src/components/Link';
import { Notice } from 'src/components/Notice/Notice';
import { RemovableSelectionsListTable } from 'src/components/RemovableSelectionsList/RemovableSelectionsListTable';
diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx
index 0eaf06dca68..d3d1326a69a 100644
--- a/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx
+++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx
@@ -1,21 +1,24 @@
+import { yupResolver } from '@hookform/resolvers/yup';
+import { FormHelperText } from '@linode/ui';
import { createSubnetSchema } from '@linode/validation';
-import { useFormik } from 'formik';
import * as React from 'react';
+import { Controller, useForm } from 'react-hook-form';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { Drawer } from 'src/components/Drawer';
import { Notice } from 'src/components/Notice/Notice';
+import { Stack } from 'src/components/Stack';
+import { TextField } from 'src/components/TextField';
import { useGrants, useProfile } from 'src/queries/profile/profile';
import { useCreateSubnetMutation, useVPCQuery } from 'src/queries/vpcs/vpcs';
-import { getErrorMap } from 'src/utilities/errorUtils';
import {
DEFAULT_SUBNET_IPV4_VALUE,
+ RESERVED_IP_NUMBER,
+ calculateAvailableIPv4sRFC1918,
getRecommendedSubnetIPv4,
} from 'src/utilities/subnets';
-import { SubnetNode } from '../VPCCreate/SubnetNode';
-
-import type { SubnetFieldState } from 'src/utilities/subnets';
+import type { CreateSubnetPayload } from '@linode/api-v4';
interface Props {
onClose: () => void;
@@ -37,86 +40,114 @@ export const SubnetCreateDrawer = (props: Props) => {
vpc?.subnets?.map((subnet) => subnet.ipv4 ?? '') ?? []
);
- const [errorMap, setErrorMap] = React.useState<
- Record
- >({});
-
const {
isPending,
mutateAsync: createSubnet,
- reset,
+ reset: resetRequest,
} = useCreateSubnetMutation(vpcId);
- const onCreateSubnet = async () => {
+ const {
+ control,
+ formState: { errors, isDirty, isSubmitting },
+ handleSubmit,
+ reset: resetForm,
+ setError,
+ watch,
+ } = useForm({
+ defaultValues: {
+ ipv4: recommendedIPv4,
+ label: '',
+ },
+ mode: 'onBlur',
+ resolver: yupResolver(createSubnetSchema),
+ });
+
+ const ipv4 = watch('ipv4');
+ const numberOfAvailableIPs = calculateAvailableIPv4sRFC1918(ipv4 ?? '');
+
+ const onCreateSubnet = async (values: CreateSubnetPayload) => {
try {
- await createSubnet({ ipv4: values.ip.ipv4, label: values.label });
+ await createSubnet(values);
onClose();
} catch (errors) {
- const newErrors = getErrorMap(['label', 'ipv4'], errors);
- setErrorMap(newErrors);
- setValues({
- ip: {
- ...values.ip,
- ipv4Error: newErrors.ipv4,
- },
- label: values.label,
- labelError: newErrors.label,
- });
+ for (const error of errors) {
+ setError(error?.field ?? 'root', { message: error.reason });
+ }
}
};
- const { dirty, handleSubmit, resetForm, setValues, values } = useFormik({
- enableReinitialize: true,
- initialValues: {
- ip: {
- availIPv4s: 256,
- ipv4: recommendedIPv4,
- },
- // @TODO VPC: add IPv6 when that is supported
- label: '',
- } as SubnetFieldState,
- onSubmit: onCreateSubnet,
- validateOnBlur: false,
- validateOnChange: false,
- validationSchema: createSubnetSchema,
- });
-
- React.useEffect(() => {
- if (open) {
- resetForm();
- reset();
- setErrorMap({});
- }
- }, [open, reset, resetForm]);
-
return (
-
- {errorMap.none && }
+ {
+ resetForm();
+ resetRequest();
+ }}
+ onClose={onClose}
+ open={open}
+ title={'Create Subnet'}
+ >
+ {errors.root?.message && (
+
+ )}
{userCannotAddSubnet && (
)}
-