diff --git a/packages/manager/.changeset/pr-10255-upcoming-features-1709738933221.md b/packages/manager/.changeset/pr-10255-upcoming-features-1709738933221.md new file mode 100644 index 00000000000..1450d3a0d51 --- /dev/null +++ b/packages/manager/.changeset/pr-10255-upcoming-features-1709738933221.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Linode plan table updates for Edge regions ([#10255](https://github.com/linode/manager/pull/10255)) diff --git a/packages/manager/src/features/Linodes/LinodesCreate/LinodeCreate.tsx b/packages/manager/src/features/Linodes/LinodesCreate/LinodeCreate.tsx index ef680a56c57..b4896c4f509 100644 --- a/packages/manager/src/features/Linodes/LinodesCreate/LinodeCreate.tsx +++ b/packages/manager/src/features/Linodes/LinodesCreate/LinodeCreate.tsx @@ -370,6 +370,11 @@ export class LinodeCreate extends React.PureComponent< }); } + const linodeIsInEdgeRegion = getIsEdgeRegion( + regionsData, + selectedRegionID ?? '' + ); + if (typeDisplayInfo) { const typeDisplayInfoCopy = cloneDeep(typeDisplayInfo); @@ -392,7 +397,17 @@ export class LinodeCreate extends React.PureComponent< )}/month $${hourlyPrice ?? UNKNOWN_PRICE}/hr`; } - displaySections.push(typeDisplayInfoCopy); + // @TODO Gecko: Remove $0 hardcoding once plan data is returned from API + if (linodeIsInEdgeRegion) { + displaySections.push({ + ...typeDisplayInfoCopy, + details: '$0/month', + hourly: 0, + monthly: 0, + }); + } else { + displaySections.push(typeDisplayInfoCopy); + } } const type = typesData.find( @@ -406,7 +421,12 @@ export class LinodeCreate extends React.PureComponent< type, }); - if (hasBackups && typeDisplayInfo && backupsMonthlyPrice) { + if ( + hasBackups && + typeDisplayInfo && + backupsMonthlyPrice && + !linodeIsInEdgeRegion + ) { displaySections.push( renderBackupsDisplaySection(accountBackupsEnabled, backupsMonthlyPrice) ); diff --git a/packages/manager/src/features/components/PlansPanel/EdgePlanTable.tsx b/packages/manager/src/features/components/PlansPanel/EdgePlanTable.tsx new file mode 100644 index 00000000000..932ac4d2f41 --- /dev/null +++ b/packages/manager/src/features/components/PlansPanel/EdgePlanTable.tsx @@ -0,0 +1,63 @@ +import { styled } from '@mui/material/styles'; +import { SxProps } from '@mui/system'; +import React from 'react'; + +import { Box } from 'src/components/Box'; +import { Notice } from 'src/components/Notice/Notice'; +import { Paper } from 'src/components/Paper'; +import { Typography } from 'src/components/Typography'; + +interface EdgePlanTableProps { + copy?: string; + docsLink?: JSX.Element; + error?: JSX.Element | string; + header: string; + innerClass?: string; + renderTable: () => React.JSX.Element; + rootClass?: string; + sx?: SxProps; +} + +export const EdgePlanTable = React.memo((props: EdgePlanTableProps) => { + const { + copy, + docsLink, + error, + header, + innerClass, + renderTable, + rootClass, + sx, + } = props; + + return ( + +
+ + {header && ( + + {header} + + )} + {docsLink} + + {error && ( + + {error} + + )} + {copy && {copy}} + {renderTable()} +
+
+ ); +}); + +const StyledTypography = styled(Typography)(({ theme }) => ({ + fontSize: '0.875rem', + marginTop: theme.spacing(1), +})); diff --git a/packages/manager/src/features/components/PlansPanel/PlanSelection.tsx b/packages/manager/src/features/components/PlansPanel/PlanSelection.tsx index 1b44a7f47ae..35532975089 100644 --- a/packages/manager/src/features/components/PlansPanel/PlanSelection.tsx +++ b/packages/manager/src/features/components/PlansPanel/PlanSelection.tsx @@ -25,6 +25,7 @@ import { StyledDisabledTableRow } from './PlansPanel.styles'; import type { PlanSelectionType } from './types'; import type { LinodeTypeClass, PriceObject, Region } from '@linode/api-v4'; + export interface PlanSelectionProps { currentPlanHeading?: string; disabled?: boolean; @@ -206,7 +207,7 @@ export const PlanSelection = (props: PlanSelectionProps) => { {type.vcpus} - {convertMegabytesTo(type.disk, true)} + {type.disk === 0 ? 'N/A' : convertMegabytesTo(type.disk, true)} {shouldShowTransfer && type.transfer ? ( diff --git a/packages/manager/src/features/components/PlansPanel/PlansPanel.tsx b/packages/manager/src/features/components/PlansPanel/PlansPanel.tsx index c0d43e6989e..72ac7f06cd7 100644 --- a/packages/manager/src/features/components/PlansPanel/PlansPanel.tsx +++ b/packages/manager/src/features/components/PlansPanel/PlansPanel.tsx @@ -1,9 +1,16 @@ import { useTheme } from '@mui/material/styles'; import * as React from 'react'; +import { useLocation } from 'react-router-dom'; +import { Notice } from 'src/components/Notice/Notice'; +import { getIsLinodeCreateTypeEdgeSupported } from 'src/components/RegionSelect/RegionSelect.utils'; +import { getIsEdgeRegion } from 'src/components/RegionSelect/RegionSelect.utils'; import { TabbedPanel } from 'src/components/TabbedPanel/TabbedPanel'; +import { useFlags } from 'src/hooks/useFlags'; import { plansNoticesUtils } from 'src/utilities/planNotices'; +import { getQueryParamsFromQueryString } from 'src/utilities/queryParams'; +import { EdgePlanTable } from './EdgePlanTable'; import { PlanContainer } from './PlanContainer'; import { PlanInformation } from './PlanInformation'; import { @@ -14,6 +21,8 @@ import { import type { PlanSelectionType } from './types'; import type { LinodeTypeClass, Region } from '@linode/api-v4'; +import type { LinodeCreateType } from 'src/features/Linodes/LinodesCreate/types'; + interface Props { className?: string; copy?: string; @@ -56,9 +65,50 @@ export const PlansPanel = (props: Props) => { types, } = props; + const flags = useFlags(); const theme = useTheme(); + const location = useLocation(); + const params = getQueryParamsFromQueryString(location.search); + + const hideEdgeRegions = + !flags.gecko || + !getIsLinodeCreateTypeEdgeSupported(params.type as LinodeCreateType); + + const showEdgePlanTable = + !hideEdgeRegions && + getIsEdgeRegion(regionsData ?? [], selectedRegionID ?? ''); + + const planTypes = getPlanSelectionsByPlanType(types); + + const getDedicatedEdgePlanType = () => { + // 256gb and 512gb plans will not be supported for Edge + const plansUpTo128GB = planTypes.dedicated.filter( + (planType) => + !['Dedicated 256 GB', 'Dedicated 512 GB'].includes( + planType.formattedLabel + ) + ); + + return plansUpTo128GB.map((plan) => { + delete plan.transfer; + return { + ...plan, + disk: 0, + price: { + hourly: 0, + monthly: 0, + }, + }; + }); + }; + + // @TODO Gecko: Get plan data from API when it's available instead of hardcoding + const plans = showEdgePlanTable + ? { + dedicated: getDedicatedEdgePlanType(), + } + : planTypes; - const plans = getPlanSelectionsByPlanType(types); const { hasSelectedRegion, isPlanPanelDisabled, @@ -83,6 +133,12 @@ export const PlansPanel = (props: Props) => { planType={plan} regionsData={regionsData || []} /> + {showEdgePlanTable && ( + + )} { currentPlanHeading ); + if (showEdgePlanTable) { + return ( + + ); + } + return (