diff --git a/app/auth/signin/page.tsx b/app/auth/signin/page.tsx deleted file mode 100644 index 5a81b640..00000000 --- a/app/auth/signin/page.tsx +++ /dev/null @@ -1,104 +0,0 @@ -'use client'; -import React from 'react'; -import { signIn } from 'next-auth/react'; -import Image from 'next/image'; -import { useSearchParams } from 'next/navigation'; -import { useMemo } from 'react'; - -import { - Background, - Container, - Content, - ExternalLink, - Form, - Header, - Panel, - Title, -} from './signin.styled'; - -import Ray from '@/assets/ray.svg'; -import TitleDark from '@/assets/titleDark.svg'; -import Vault from '@/assets/vault.svg'; -import Button from '@/components/Button/Button'; -import Typography from '@/components/Typography/Typography'; -import { VOLCANIC_SAND } from '@/constants/colors'; -import NextLink from '@/components/NextLink/NextLink'; - -export default function SignIn() { - const searchParams = useSearchParams(); - const callbackUrlParam = searchParams?.get('callbackUrl'); - - const callbackUrl = useMemo( - () => - callbackUrlParam && callbackUrlParam.includes('error') ? location.origin : callbackUrlParam, - [callbackUrlParam], - ); - - return ( - - -
- Get started with your instant Kubernetes Platform! - k1-ray-image -
- - By using the Kubefirst platform, you agree to our{' '} - - Terms of Service{' '} - - and{' '} - - Privacy Policy. - - -
- -
- - k1-image - - Welcome - - - To log in to your kubefirst platform please use your Vault credentials. - - - - - Help and Support - -
-
-
- ); -} diff --git a/app/auth/signin/signin.styled.ts b/app/auth/signin/signin.styled.ts deleted file mode 100644 index baa0f137..00000000 --- a/app/auth/signin/signin.styled.ts +++ /dev/null @@ -1,73 +0,0 @@ -'use client'; -import styled from 'styled-components'; -import Box from '@mui/material/Box'; -import Link from 'next/link'; - -import Typography from '@/components/Typography/Typography'; -import { CELERY_MOUSSE } from '@/constants/colors'; -import { media } from '@/utils/media'; -import Column from '@/components/Column/Column'; - -export const Background = styled.div` - align-items: center; - background: linear-gradient(to right, #181626 19%, #3c356c 72.6%, #d0bae9 180%); - display: none; - flex-direction: column; - padding-bottom: 34px; - padding-top: 80px; - width: calc(100% - 400px); - justify-content: space-between; - - ${media.greaterThan('md')` - display: flex; - `} -`; - -export const Header = styled.div` - align-items: center; - display: flex; - flex-direction: column; - gap: 80px; -`; - -export const Container = styled.div` - display: flex; - height: 100%; - width: 100%; -`; - -export const Content = styled(Column)` - align-items: center; - justify-content: center; -`; - -export const Form = styled(Box)` - align-items: center; - display: flex; - flex-direction: column; - width: 100%; -`; - -export const Panel = styled.div` - align-items: center; - display: flex; - flex-direction: column; - padding: 40px; - position: relative; - width: 400; -`; - -export const Title = styled(Typography)` - color: ${({ theme }) => theme.colors.white}; - text-align: center; - width: 478px; -`; - -export const ExternalLink = styled(Link)` - color: ${CELERY_MOUSSE}; - text-decoration: none; - - &:hover { - text-decoration: underline; - } -`; diff --git a/app/dashboard/applications/page.tsx b/app/dashboard/applications/page.tsx deleted file mode 100644 index 71387476..00000000 --- a/app/dashboard/applications/page.tsx +++ /dev/null @@ -1,30 +0,0 @@ -'use client'; -import React, { FunctionComponent, useMemo } from 'react'; -import { useRouter } from 'next/navigation'; - -import Applications from '../../../containers/Applications/Applications'; -import { useAppSelector } from '../../../redux/store'; - -import { Route } from '@/constants'; - -export interface ApplicationsPageProps { - isClusterZero: boolean; -} - -const ApplicationsPage: FunctionComponent = ({ isClusterZero }) => { - const { push } = useRouter(); - - const { managementCluster } = useAppSelector(({ api }) => api); - - const hasExistingCluster = useMemo( - () => !isClusterZero || managementCluster, - [isClusterZero, managementCluster], - ); - - if (!hasExistingCluster) { - push(Route.HOME); - } - - return hasExistingCluster ? : null; -}; -export default ApplicationsPage; diff --git a/app/dashboard/cluster-management/page.tsx b/app/dashboard/cluster-management/page.tsx deleted file mode 100644 index c89df34c..00000000 --- a/app/dashboard/cluster-management/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -import ClusterManagement from '@/containers/ClusterManagement/ClusterManagement'; - -const ClusterManagementPage = () => { - const isClusterZero = process.env.IS_CLUSTER_ZERO === 'true'; - - return !isClusterZero && ; -}; - -export default ClusterManagementPage; diff --git a/app/dashboard/environments/page.tsx b/app/dashboard/environments/page.tsx deleted file mode 100644 index 0de7893a..00000000 --- a/app/dashboard/environments/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -'use client'; -import React, { FunctionComponent } from 'react'; - -import Environments from '@/containers/Environments/Environments'; - -const EnvironmentsPage: FunctionComponent = () => { - return ; -}; - -export default EnvironmentsPage; diff --git a/app/dashboard/layout.tsx b/app/dashboard/layout.tsx deleted file mode 100644 index d3aa17b9..00000000 --- a/app/dashboard/layout.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { PropsWithChildren } from 'react'; - -import { getEnvVars, getFeatureFlags, validateLicense } from '../lib/common'; - -import { Layout } from '@/containers/Layout/Layout'; - -export default async function Page({ children }: PropsWithChildren) { - const license = await validateLicense(); - const envVariables = await getEnvVars(); - const featureFlags = await getFeatureFlags(); - - return ( - - {children} - - ); -} diff --git a/app/layout.tsx b/app/layout.tsx index fb6a9944..6e1e99e7 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,11 +1,9 @@ import React, { PropsWithChildren } from 'react'; -import { getServerSession } from 'next-auth/next'; -import { Session } from 'next-auth'; import { Providers } from '@/app/lib/providers'; import StyledComponentsRegistry from '@/app/lib/registry'; -import { authOptions } from '@/pages/api/auth/[...nextauth]'; - +import { Layout } from '@/containers/Layout/Layout'; +import { getEnvVars, getFeatureFlags } from '@/app/lib/common'; import '../styles/globals.css'; export const metadata = { @@ -20,13 +18,18 @@ export const metadata = { }; export default async function RootLayout({ children }: PropsWithChildren) { - const session = await getServerSession(authOptions); + const envVariables = await getEnvVars(); + const featureFlags = await getFeatureFlags(); return ( - - {children} + + + + {children} + + diff --git a/app/lib/common.ts b/app/lib/common.ts index 4ac73955..6b8b5ae5 100644 --- a/app/lib/common.ts +++ b/app/lib/common.ts @@ -1,23 +1,7 @@ -import axios from 'axios'; import { PostHog } from 'posthog-node'; -import { License } from '@/types/subscription'; import { EnvironmentVariables, FeatureFlag } from '@/types/config'; -const { ENTERPRISE_API_URL } = process.env; - -export async function validateLicense() { - try { - const response = await axios.post( - `${ENTERPRISE_API_URL}/api/v1/subscription/validate`, - ); - return response.data; - } catch (error) { - // supressing error. license not found - return {} as License; - } -} - export async function getFeatureFlags() { try { const { KUBEFIRST_VERSION = '', POSTHOG_KEY = '' } = process.env; @@ -52,7 +36,6 @@ export async function getEnvVars() { IS_CLUSTER_ZERO = '', KUBEFIRST_VERSION = '', POSTHOG_KEY = '', - SAAS_URL = '', } = process.env; return { @@ -64,7 +47,6 @@ export async function getEnvVars() { isClusterZero: IS_CLUSTER_ZERO === 'true', kubefirstVersion: KUBEFIRST_VERSION, installMethod: INSTALL_METHOD, - saasURL: SAAS_URL, POSTHOG_KEY, }; } catch (error) { diff --git a/app/lib/providers.tsx b/app/lib/providers.tsx index 3c883835..53a350b0 100644 --- a/app/lib/providers.tsx +++ b/app/lib/providers.tsx @@ -2,8 +2,6 @@ import React, { PropsWithChildren } from 'react'; import { Provider } from 'react-redux'; -import { Session } from 'next-auth'; -import { SessionProvider } from 'next-auth/react'; import { ThemeProvider as ThemeProviderMUI } from '@mui/material/styles'; import { PersistGate } from 'redux-persist/integration/react'; @@ -14,20 +12,18 @@ import { persistor, store } from '@/redux/store'; import { muiTheme } from '@/theme/muiTheme'; import { theme } from '@/theme/index'; -export function Providers({ children, session }: PropsWithChildren<{ session: Session | null }>) { +export function Providers({ children }: PropsWithChildren) { return ( - - - - - - - {children} - - - - - - + + + + + + {children} + + + + + ); } diff --git a/app/page.tsx b/app/page.tsx index ec3f4b44..41c9157b 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,30 +1,7 @@ import React from 'react'; -import { Session, getServerSession } from 'next-auth'; -import { redirect } from 'next/navigation'; -import Box from '@mui/material/Box'; -import CircularProgress from '@mui/material/CircularProgress'; -import { authOptions } from '@/pages/api/auth/[...nextauth]'; -import { Route } from '@/constants'; +import Provision from '@/containers/Provision/Provision'; export default async function MainPage() { - const session = await getServerSession(authOptions); - const isClusterZero = process.env.IS_CLUSTER_ZERO === 'true'; - const isAuthDisabled = process.env.DISABLE_AUTH === 'true'; - - if (!session && !isClusterZero && !isAuthDisabled) { - return redirect(Route.SIGN_IN); - } - - if (isAuthDisabled) { - return redirect(Route.APPLICATIONS); - } - - redirect(isClusterZero ? Route.PROVISION : Route.CLUSTER_MANAGEMENT); - - return ( - - - - ); + return ; } diff --git a/app/provision/layout.tsx b/app/provision/layout.tsx deleted file mode 100644 index d3aa17b9..00000000 --- a/app/provision/layout.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { PropsWithChildren } from 'react'; - -import { getEnvVars, getFeatureFlags, validateLicense } from '../lib/common'; - -import { Layout } from '@/containers/Layout/Layout'; - -export default async function Page({ children }: PropsWithChildren) { - const license = await validateLicense(); - const envVariables = await getEnvVars(); - const featureFlags = await getFeatureFlags(); - - return ( - - {children} - - ); -} diff --git a/app/provision/page.tsx b/app/provision/page.tsx deleted file mode 100644 index fafdb5f5..00000000 --- a/app/provision/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -'use client'; -import React, { FunctionComponent } from 'react'; - -import Provision from '@/containers/Provision/Provision'; - -const ProvisionPage: FunctionComponent = () => { - return ; -}; - -export default ProvisionPage; diff --git a/app/settings/git-account/page.tsx b/app/settings/git-account/page.tsx deleted file mode 100644 index d2d01903..00000000 --- a/app/settings/git-account/page.tsx +++ /dev/null @@ -1,20 +0,0 @@ -'use client'; -import { FunctionComponent } from 'react'; -import { useRouter } from 'next/navigation'; - -// import GitAccount from '@/containers/GitAccount/GitAccount'; -// import { useAppSelector } from '@/redux/store'; -import { Route } from '@/constants'; - -const GitAccountPage: FunctionComponent = () => { - // const { managementCluster } = useAppSelector(({ api }) => api); - const { push } = useRouter(); - - push(Route.HOME); - - return null; - - // return managementCluster ? : null; -}; - -export default GitAccountPage; diff --git a/app/settings/layout.tsx b/app/settings/layout.tsx deleted file mode 100644 index d3aa17b9..00000000 --- a/app/settings/layout.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { PropsWithChildren } from 'react'; - -import { getEnvVars, getFeatureFlags, validateLicense } from '../lib/common'; - -import { Layout } from '@/containers/Layout/Layout'; - -export default async function Page({ children }: PropsWithChildren) { - const license = await validateLicense(); - const envVariables = await getEnvVars(); - const featureFlags = await getFeatureFlags(); - - return ( - - {children} - - ); -} diff --git a/app/settings/subscription/[slug]/page.tsx b/app/settings/subscription/[slug]/page.tsx deleted file mode 100644 index e7a56a06..00000000 --- a/app/settings/subscription/[slug]/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -import { getProductPlans } from '../page'; - -import Subscription from '@/containers/Subscription/Subscription'; - -export default async function Page({ params }: { params: { slug: string } }) { - const plans = await getProductPlans(); - - return ; -} diff --git a/app/settings/subscription/page.tsx b/app/settings/subscription/page.tsx deleted file mode 100644 index dd936d75..00000000 --- a/app/settings/subscription/page.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import axios from 'axios'; -import sortBy from 'lodash/sortBy'; - -import Subscription from '@/containers/Subscription/Subscription'; -import { Plan } from '@/types/plan'; - -export async function getProductPlans() { - const { SAAS_API_URL = '' } = process.env; - - try { - const { data } = await axios.get>(`${SAAS_API_URL}/api/v1/payment/plans`); - - return sortBy(data, (product) => product && product.metadata && product.metadata['order']); - } catch (error) { - // eslint-disable-next-line no-console - console.log('error getting product plans', error); - return []; - } -} - -export default async function Page() { - const plans = await getProductPlans(); - - return ; -} diff --git a/charts/console/templates/deployment.yaml b/charts/console/templates/deployment.yaml index 4516a773..6c4c3a42 100644 --- a/charts/console/templates/deployment.yaml +++ b/charts/console/templates/deployment.yaml @@ -33,8 +33,6 @@ spec: env: - name: API_URL value: {{ .Values.apiURL | default (printf "http://%s-kubefirst-api.%s.svc.cluster.local" (.Release.Name ) (.Release.Namespace )) }} - - name: ENTERPRISE_API_URL - value: {{ .Values.enterpriseApiURL | default (printf "http://%s-kubefirst-api-ee.%s.svc.cluster.local" (.Release.Name ) (.Release.Namespace )) }} - name: KUBEFIRST_VERSION value: {{ .Values.global.kubefirstVersion }} - name: IS_CLUSTER_ZERO @@ -49,10 +47,6 @@ spec: value: "0.0.0.0" - name: NEXTAUTH_URL value: "{{ .Values.authURL | default (printf "https://kubefirst.%s" (.Values.domain )) }}" - - name: SAAS_API_URL - value: "{{ .Values.global.saasAPIUrl | default "https://api.konstruct.io" }}" - - name: SAAS_URL - value: "{{ .Values.global.saasUrl | default "https://dashboard.konstruct.io" }}" - name: K1_ACCESS_TOKEN valueFrom: secretKeyRef: diff --git a/components/Application/Application.stories.tsx b/components/Application/Application.stories.tsx deleted file mode 100644 index 36c9d209..00000000 --- a/components/Application/Application.stories.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; - -import Application from './Application'; - -import { mockClusterApplication } from '@/tests/mocks/mockClusterApplication'; - -const meta: Meta = { - component: Application, -}; - -export default meta; - -export const Default: StoryObj = { - args: { - ...mockClusterApplication, - links: mockClusterApplication.links.reduce( - (previous, current) => ({ ...previous, [current]: true }), - {}, - ), - }, -}; diff --git a/components/Application/Application.styled.ts b/components/Application/Application.styled.ts deleted file mode 100644 index 9749f971..00000000 --- a/components/Application/Application.styled.ts +++ /dev/null @@ -1,103 +0,0 @@ -import NextImage from 'next/image'; -import NextLink from 'next/link'; - -import { textTruncate } from '@/utils/theme'; -import { PASTEL_LIGHT_BLUE } from '@/constants/colors'; -import Typography from '@/components/Typography/Typography'; -import styled, { css } from '@/app/lib/styled-components'; - -export const AppConnector = styled.div` - height: 16px; - background-color: ${({ theme }) => theme.colors.pastelLightBlue}; - top: 8px; - left: 3px; - position: absolute; - width: 2px; -`; - -export const LiveAppIcon = styled.div` - border-radius: 50%; - background-color: ${({ color }) => color}; - height: 8px; - position: relative; - width: 8px; -`; - -export const Container = styled.div` - background: ${({ theme }) => theme.colors.white}; - border: 1px solid ${PASTEL_LIGHT_BLUE}; - border-radius: 12px; - min-height: 194px; - padding: 24px; - width: 310px; -`; - -export const Description = styled(Typography)` - color: ${({ theme }) => theme.colors.saltboxBlue}; - height: 60px; - - ${textTruncate(3)}; -`; - -export const Header = styled.div` - align-items: center; - display: flex; - flex-direction: row; - gap: 16px; - height: 35px; - margin-bottom: 10px; -`; - -export const Image = styled(NextImage)` - object-fit: contain; - width: 24px; -`; - -export const Title = styled(Typography)` - color: ${({ theme }) => theme.colors.volcanicSand}; - font-weight: 600; - text-transform: capitalize; -`; - -export const Link = styled(NextLink)<{ disabled?: boolean }>` - align-items: center; - color: ${({ theme }) => theme.colors.primary}; - display: flex; - text-decoration: none; - gap: 8px; - - & > span { - overflow: hidden; - max-width: 400px; - text-overflow: ellipsis; - - ${textTruncate(1)}; - } - - &:hover { - text-decoration: underline; - } - - ${({ disabled }) => - disabled && - css` - color: #a1a1aa; - cursor: not-allowed; - - &:hover { - text-decoration: none; - } - `} -`; - -export const Links = styled.div` - cursor: pointer; - display: flex; - flex-direction: column; - margin-top: 24px; - - & > span:last-child > a > div > div, - & > a:last-child > div > div { - display: none; - } -`; diff --git a/components/Application/Application.tsx b/components/Application/Application.tsx deleted file mode 100644 index afe01e32..00000000 --- a/components/Application/Application.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import React, { FunctionComponent, useCallback, useMemo } from 'react'; -import Box from '@mui/material/Box'; -import CircularProgress from '@mui/material/CircularProgress'; - -import Typography from '../Typography/Typography'; -import Tooltip from '../Tooltip/Tooltip'; -import Button from '../Button/Button'; - -import { - AppConnector, - Container, - Description, - Header, - Image, - Link, - Links, - LiveAppIcon, - Title, -} from './Application.styled'; - -import { formatDomain } from '@/utils/url/formatDomain'; -import { MINT_GREEN, PASTEL_LIGHT_BLUE } from '@/constants/colors'; -import { noop } from '@/utils/noop'; - -export interface ApplicationProps { - description?: string; - default: boolean; - children?: React.ReactNode; - image: string; - name: string; - links?: { [url: string]: boolean }; - onLinkClick: (link: string, name: string) => void; - onUninstall: () => void; - isUninstalling: boolean; -} - -const Application: FunctionComponent = ({ - description, - default: defaultApp, - children, - image, - name, - links, - onLinkClick, - onUninstall = noop, - isUninstalling, -}) => { - const isMetaphor = useMemo(() => name === 'Metaphor', [name]); - - const showTooltip = useMemo( - () => (description ? description.length > 167 : false), - [description], - ); - - const ApplicationLink = useCallback( - (link: string, isAvailable?: boolean) => { - return link ? ( - { - if (isAvailable) { - onLinkClick(link, name); - } else { - e.preventDefault(); - e.stopPropagation(); - } - }} - target="_blank" - disabled={!isAvailable} - > - - {isMetaphor && } - - {formatDomain(link, isMetaphor)} - {!isAvailable && ( - - - - )} - - ) : ( -
- ); - }, - [isMetaphor, name, onLinkClick], - ); - - const linksComponent = useMemo( - () => ( - - {links && - Object.keys(links)?.map((url) => { - const isAvailable = links[url]; - const { origin, pathname } = (url?.includes('http') && new URL(url)) || { - origin: '', - pathname: '', - }; - const shouldUseTooltip = pathname.length > 40 || origin.length > 40; - const linkComponent = ApplicationLink(url, isAvailable); - - return shouldUseTooltip ? ( - - {linkComponent} - - ) : ( - linkComponent - ); - })} - - ), - [links, ApplicationLink], - ); - - return ( - -
- {name} - {name} -
- {showTooltip ? ( - - {description} - - ) : ( - {description} - )}{' '} - {links && !children ? linksComponent : children} - {!defaultApp && ( - - )} -
- ); -}; - -export default Application; diff --git a/components/ApplicationsFilter/ApplicationsFilter.stories.tsx b/components/ApplicationsFilter/ApplicationsFilter.stories.tsx deleted file mode 100644 index eaa65dd7..00000000 --- a/components/ApplicationsFilter/ApplicationsFilter.stories.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; - -import ApplicationsFilter from './ApplicationsFilter'; - -const meta: Meta = { - component: ApplicationsFilter, -}; - -export default meta; - -export const Default: StoryObj = { - args: { - targetOptions: [{ value: 'yes', label: 'yes' }], - clusterSelectOptions: [{ value: 'yes', label: 'yes' }], - searchOptions: [{ value: 'yes', label: 'yes' }], - }, -}; diff --git a/components/ApplicationsFilter/ApplicationsFilter.styled.ts b/components/ApplicationsFilter/ApplicationsFilter.styled.ts deleted file mode 100644 index 7928fe26..00000000 --- a/components/ApplicationsFilter/ApplicationsFilter.styled.ts +++ /dev/null @@ -1,50 +0,0 @@ -import styled from 'styled-components'; -import { styled as muiStyled } from '@mui/material'; - -import Column from '../Column/Column'; -import Autocomplete from '../Autocomplete/Autocomplete'; -import Row from '../Row/Row'; - -import { PASTEL_LIGHT_BLUE, WHITE } from '@/constants/colors'; -import { media } from '@/utils/media'; - -export const Container = styled(Column)` - padding: 24px; - background-color: ${WHITE}; - border: 1px solid ${PASTEL_LIGHT_BLUE}; - border-radius: 8px; -`; - -export const Content = styled(Column)` - gap: 16px; - width: fit-content; - align-items: flex-end; - - ${media.greaterThan('md')` - flex-direction: row; - width: unset; - justify-content: space-between; - `} -`; - -export const DropdownContainer = styled(Column)` - align-items: flex-end; - justify-content: end; - width: fit-content; - gap: 16px; - - ${media.greaterThan('md')` - flex-direction: row; - `} -`; - -export const TargetContainer = styled(Row)` - gap: 8px; - align-items: center; -`; - -export const StyledAutoComplete = muiStyled(Autocomplete)(() => ({ - 'width': '248px', - 'height': '36px', - '& .MuiAutocomplete-popupIndicator': { transform: 'none' }, -})); diff --git a/components/ApplicationsFilter/ApplicationsFilter.tsx b/components/ApplicationsFilter/ApplicationsFilter.tsx deleted file mode 100644 index 85f7e23e..00000000 --- a/components/ApplicationsFilter/ApplicationsFilter.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import React, { - ChangeEvent, - ComponentPropsWithoutRef, - FunctionComponent, - useEffect, - useState, -} from 'react'; -import styled from 'styled-components'; -import SearchIcon from '@mui/icons-material/Search'; - -import Row from '../Row/Row'; -import Typography from '../Typography/Typography'; -import { IAutocompleteProps } from '../Autocomplete/Autocomplete'; -import Select from '../Select/Select'; -import TextFieldWithRef from '../TextField/TextField'; -import { InputAdornmentContainer } from '../TextField/TextField.styled'; - -import { - Container, - Content, - DropdownContainer, - TargetContainer, -} from './ApplicationsFilter.styled'; - -import { VOLCANIC_SAND } from '@/constants/colors'; -import { LabelValue } from '@/types'; -import { noop } from '@/utils/noop'; -import { ApplicationsState } from '@/redux/slices/applications.slice'; -import { Target } from '@/types/applications'; - -export interface ApplicationsFilterProps extends ComponentPropsWithoutRef<'div'> { - searchOptions: IAutocompleteProps['options']; - targetOptions: LabelValue[]; - clusterSelectOptions: LabelValue[]; - onFilterChange?: (filter: ApplicationsState['filter']) => void; - onSearchChange: (filter: string) => void; - defaultCluster: string; -} - -const ApplicationsFilter: FunctionComponent = ({ - clusterSelectOptions, - defaultCluster, - onFilterChange = noop, - onSearchChange, - targetOptions, - ...rest -}) => { - const [searchTerm, setSearchTerm] = useState(''); - const [target, setTarget] = useState(Target.CLUSTER); - const [cluster, setCluster] = useState(defaultCluster); - - const handleChangeTarget = (value: Target) => { - setTarget(value); - setCluster(''); - - onFilterChange({ target: value, cluster }); - }; - - const handleChangeCluster = (value: string) => { - setCluster(value); - onFilterChange({ target, cluster: value }); - }; - - const handleOnChangeSearch = (event: ChangeEvent) => { - setSearchTerm(event.target.value); - onSearchChange(event.target.value); - }; - - useEffect(() => { - if (defaultCluster && clusterSelectOptions.length) { - onFilterChange({ target, cluster: defaultCluster }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - - - - - - Target: - - handleChangeCluster(e.target.value)} - defaultValue={defaultCluster} - sx={{ width: '248px' }} - /> - )} - - - - - - - } - placeholder="Search app name" - value={searchTerm} - onChange={handleOnChangeSearch} - sx={{ width: '248px' }} - /> - - - - ); -}; - -export default styled(ApplicationsFilter)``; diff --git a/components/Autocomplete/Autocomplete.tsx b/components/Autocomplete/Autocomplete.tsx index aa5ac3db..51b232ab 100644 --- a/components/Autocomplete/Autocomplete.tsx +++ b/components/Autocomplete/Autocomplete.tsx @@ -1,33 +1,14 @@ import React, { ForwardedRef, FunctionComponent, useMemo } from 'react'; import CircularProgress from '@mui/material/CircularProgress'; -import AddIcon from '@mui/icons-material/Add'; import AutocompleteMUI, { autocompleteClasses } from '@mui/material/Autocomplete'; import SearchIcon from '@mui/icons-material/Search'; import { SxProps } from '@mui/system'; import { ControllerRenderProps, FieldValues } from 'react-hook-form'; -import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; -import { InputLabel } from '@mui/material'; import omit from 'lodash/omit'; import TextField from '../TextField/TextField'; -import Column from '../Column/Column'; -import Typography from '../Typography/Typography'; -import { Required } from '../TextField/TextField.styled'; -import Row from '../Row/Row'; -import Tag from '../Tag/Tag'; -import { AutoTextField, InputAdornmentContainer, Label, MenuItem } from './Autocomplete.styled'; - -import { ClusterEnvironment } from '@/types/provision'; -import { noop } from '@/utils/noop'; -import { ROYAL_PURPLE } from '@/constants/colors'; - -const NEW_ENV: ClusterEnvironment = { - id: 'create env', - name: 'create env', - color: 'cyan', - creationDate: 'now', -}; +import { InputAdornmentContainer } from './Autocomplete.styled'; type AutocompleteOption = { value: unknown; label: string }; @@ -109,105 +90,8 @@ const AutocompleteComponent: FunctionComponent = ({ ); }; -export interface AutocompleteTagsProps - extends Omit { - options: ClusterEnvironment[]; - onTagDelete: () => void; - onChange: (value?: ClusterEnvironment) => void; - createEnvironment?: boolean; - onAddNewEnvironment?: () => void; -} - -const AutocompleteTagsComponent: FunctionComponent = ({ - options, - value, - required, - disabled, - createEnvironment, - label, - onChange, - onTagDelete, - onAddNewEnvironment = noop, -}) => { - return ( - option.name} - onChange={(_, options) => { - const [option] = options.reverse(); - onChange(option?.name !== NEW_ENV.name ? option : undefined); - }} - isOptionEqualToValue={(option: ClusterEnvironment) => { - return option.name === value?.name; - }} - popupIcon={} - ListboxProps={{ - style: { - maxHeight: '210px', - }, - }} - renderInput={(params) => ( - - - - - - - )} - renderOption={({ onClick = noop, ...rest }, option) => { - const createNewEnvironment = option.name === NEW_ENV.name; - - return ( - { - if (createNewEnvironment) { - onAddNewEnvironment(); - } - onClick(e); - }} - > - {createNewEnvironment ? ( - - - - New environment - - - ) : ( - - )} - - ); - }} - renderTags={(tags) => - tags.map((option, index) => ( - - )) - } - /> - ); -}; - const Autocomplete = React.forwardRef((props, ref) => { return ; }); -export const AutocompleteTags = React.forwardRef((props, ref) => { - return ; -}); - export default Autocomplete; diff --git a/components/Autocomplete/TagsAutocomplete.stories.tsx b/components/Autocomplete/TagsAutocomplete.stories.tsx deleted file mode 100644 index 41123649..00000000 --- a/components/Autocomplete/TagsAutocomplete.stories.tsx +++ /dev/null @@ -1,65 +0,0 @@ -'use client'; -import React, { FunctionComponent, useCallback, useEffect } from 'react'; -import { Meta, StoryObj } from '@storybook/react'; -import { useForm } from 'react-hook-form'; - -import ControlledTagsAutocomplete from '../controlledFields/ControlledAutoComplete/ControlledTagsAutoComplete'; - -import { createEnvMap } from '@/utils/createEnvMap'; -import { mockEnvironmentsResponse } from '@/tests/mocks/mockEnvironmentsResponse'; -import { ClusterEnvironment } from '@/types/provision'; - -const mockEnvironments = Object.values(createEnvMap(mockEnvironmentsResponse)); - -const meta: Meta = { - component: ControlledTagsAutocomplete, -}; - -export default meta; - -const AutoCompleteTagsWithHooks: FunctionComponent<{ createEnvironment?: boolean }> = ({ - createEnvironment, -}) => { - const { control, setValue, watch } = useForm<{ environment?: ClusterEnvironment }>(); - - const handleChange = useCallback( - (env?: ClusterEnvironment) => { - setValue('environment', env); - }, - [setValue], - ); - - const handleTagDelete = useCallback(() => { - setValue('environment', undefined); - }, [setValue]); - - // eslint-disable-next-line no-console - const handleAddNewEnvironment = useCallback(() => console.log('add new environment'), []); - - useEffect(() => { - // eslint-disable-next-line no-console - const subscription = watch(({ environment }) => console.log(environment)); - return () => subscription.unsubscribe(); - }, [watch]); - - return ( - - ); -}; - -export const Default: StoryObj = { - render: (props) => , - args: { - createEnvironment: false, - }, -}; diff --git a/components/CloudProviderCard/CloudProviderCard.tsx b/components/CloudProviderCard/CloudProviderCard.tsx index e8eee48d..039a6e81 100644 --- a/components/CloudProviderCard/CloudProviderCard.tsx +++ b/components/CloudProviderCard/CloudProviderCard.tsx @@ -90,7 +90,7 @@ const PROVIDER_OPTIONS: Record< }, [InstallationType.GOOGLE]: { logoSrc: googleCloudLogo, - label: 'Google Cloud Platform (GCP)', + label: 'Google Cloud', description: 'High-performance infrastructure for cloud computing, data analytics & machine learning. Secure, reliable and high performance cloud services.', learnMoreLink: 'https://cloud.google.com/', diff --git a/components/ClusterDetails/ClusterDetails.stories.tsx b/components/ClusterDetails/ClusterDetails.stories.tsx deleted file mode 100644 index d43e7d47..00000000 --- a/components/ClusterDetails/ClusterDetails.stories.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; - -import ClusterDetails from './ClusterDetails'; - -import { mockClusterConfig } from '@/tests/mocks/mockClusterConfig'; -import { ClusterStatus, ClusterType } from '@/types/provision'; -import { GitProvider } from '@/types'; -import { InstallationType } from '@/types/redux'; - -const meta: Meta = { - component: ClusterDetails, -}; - -export default meta; - -export const Default: StoryObj = { - args: { - cluster: { - ...mockClusterConfig, - clusterId: '1', - status: ClusterStatus.PROVISIONED, - clusterName: 'man-cluster-1', - cloudProvider: InstallationType.CIVO, - adminEmail: 'derrick@kubeshop.io', - domainName: 'kubefirst.io', - dnsProvider: 'civo', - gitProvider: GitProvider.GITHUB, - gitAuth: { - gitOwner: 'D-B-Hawk', - gitToken: 'kray', - gitUser: 'D-B-Hawk', - }, - type: mockClusterConfig.type as ClusterType, - environment: { - name: 'Demo', - color: 'dark-sky-blue', - description: 'Environment for demoing to prospective customers.', - }, - }, - }, -}; diff --git a/components/ClusterDetails/ClusterDetails.styled.ts b/components/ClusterDetails/ClusterDetails.styled.ts deleted file mode 100644 index 2876af0d..00000000 --- a/components/ClusterDetails/ClusterDetails.styled.ts +++ /dev/null @@ -1,82 +0,0 @@ -'use client'; -import styled, { css } from 'styled-components'; -import { styled as muiStyled } from '@mui/material/styles'; -import { typographyClasses } from '@mui/material/Typography'; -import { Divider } from '@mui/material'; - -import ColumnComponent from '@/components/Column/Column'; -import RowComponent from '@/components/Row/Row'; -import Typography from '@/components/Typography/Typography'; -import { - EXCLUSIVE_PLUM, - PASTEL_LIGHT_BLUE, - PRIMARY, - SPUN_PEARL, - VOLCANIC_SAND, -} from '@/constants/colors'; - -export const Container = styled(ColumnComponent)` - width: 100%; -`; - -export const Content = styled(ColumnComponent)` - gap: 24px; -`; - -export const ColumnInfo = styled(ColumnComponent)` - gap: 8px; - justify-content: space-between; -`; - -export const ExternalLink = styled.a.attrs({ - target: '_blank', - rel: 'noreferrer', -})<{ available?: boolean }>` - font-size: 14px; - text-decoration: none; - color: ${({ available }) => (available ? PRIMARY : SPUN_PEARL)}; - cursor: pointer; - - ${({ available }) => - !available && - css` - pointer-events: none; - `} - - &:hover { - text-decoration: underline; - } -`; - -export const Link = styled.a` - display: inline-flex; - color: ${PRIMARY}; -`; - -export const RowInfo = styled(RowComponent)` - gap: 24px; -`; - -export const StatusContainer = styled(ColumnComponent)` - gap: 4px; - padding: 16px 0; -`; - -export const StyledDivider = muiStyled(Divider)( - () => ` - border-color: ${PASTEL_LIGHT_BLUE} -`, -); - -export const StyledLabel = muiStyled(Typography)(() => ({ - [`&.${typographyClasses.root}`]: { - color: EXCLUSIVE_PLUM, - width: '150px', - }, -})); - -export const StyledValue = muiStyled(Typography)(() => ({ - [`&.${typographyClasses.root}`]: { - color: VOLCANIC_SAND, - }, -})); diff --git a/components/ClusterDetails/ClusterDetails.tsx b/components/ClusterDetails/ClusterDetails.tsx deleted file mode 100644 index de98d5ad..00000000 --- a/components/ClusterDetails/ClusterDetails.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import React, { - ComponentPropsWithoutRef, - FunctionComponent, - useEffect, - useMemo, - useState, -} from 'react'; -import moment from 'moment'; - -import { - Container, - Content, - RowInfo, - StyledLabel, - StyledValue, - ExternalLink, - StyledDivider, - StatusContainer, -} from './ClusterDetails.styled'; - -import Typography from '@/components/Typography/Typography'; -import { - Cluster, - ClusterStatus, - ClusterType, - DraftCluster, - ManagementCluster, -} from '@/types/provision'; -import StatusIndicator from '@/components/StatusIndicator/StatusIndicator'; -import { CLOUD_PROVIDER_DISPLAY_NAME, GIT_PROVIDER_DISPLAY_NAME } from '@/constants'; -import Tag from '@/components/Tag/Tag'; - -export interface ClusterDetailsProps extends Omit, 'key'> { - cluster: Cluster | DraftCluster; - host: ManagementCluster['gitHost']; - gitOwner: ManagementCluster['gitAuth']['gitOwner']; -} - -const ClusterDetails: FunctionComponent = ({ - cluster, - host, - gitOwner, - ...rest -}) => { - const { - adminEmail, - clusterName, - cloudProvider, - cloudRegion, - creationDate, - domainName, - subDomainName, - gitProvider, - nodeCount, - instanceSize, - environment, - type, - status, - gitAuth: { gitUser } = {}, - } = cluster; - - const [available, setAvailable] = useState(status === ClusterStatus.PROVISIONED ?? false); - - useEffect(() => { - setTimeout(() => setAvailable(true), 10000); - }); - - const fullDomainName = useMemo( - () => (subDomainName ? `${subDomainName}.${domainName}` : domainName), - [subDomainName, domainName], - ); - - return ( - - - - - View your Argo CD clusters - - - - - View your {gitProvider} cluster configuration - - - - - - - - Details - - - - {/* Domain name */} - - Cluster domain name - {fullDomainName} - - {/* Git provider */} - - Git provider - {GIT_PROVIDER_DISPLAY_NAME[gitProvider]} - - {/* Environments */} - - Environments - {environment?.name && } - - {/* Cloud account */} - - Cloud account - {CLOUD_PROVIDER_DISPLAY_NAME[cloudProvider]} - - {/* Cloud region */} - - Cloud region - {cloudRegion} - - {/* Instance size */} - - Instance size - {instanceSize && {instanceSize.toUpperCase()}} - - {/* Number of nodes */} - {type !== ClusterType.WORKLOAD_V_CLUSTER && ( - - Number of nodes - {nodeCount} - - )} - {/* Alerts email */} - - Alerts email - {adminEmail} - - {/* Created */} - - Created - - {creationDate && moment(+creationDate).format('DD MMM YYYY, HH:MM:SS')} - - - {/* Created by */} - - Created by - {gitUser} - - - - ); -}; - -export default ClusterDetails; diff --git a/components/ClusterMap/ClusterMap.tsx b/components/ClusterMap/ClusterMap.tsx deleted file mode 100644 index 6ca2a2f8..00000000 --- a/components/ClusterMap/ClusterMap.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React, { FunctionComponent } from 'react'; -import ReactFlow, { Background, Controls } from 'reactflow'; - -import 'reactflow/dist/style.css'; - -export interface ClusterMapProps { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - clusters: Array; -} - -const ClusterMap: FunctionComponent = ({ clusters }) => { - if (!clusters) { - return null; - } - - const nodes = [ - { - id: '1', - data: { label: 'Kubefirst' }, - position: { x: 0, y: 0 }, - type: 'input', - }, - ...clusters.map(({ ClusterName }, index) => ({ - id: ClusterName, - data: { label: ClusterName }, - position: { x: index * 100, y: index * 100 }, - })), - ]; - - return ( -
- - - - -
- ); -}; - -export default ClusterMap; diff --git a/components/ClusterTable/ClusterTable.stories.tsx b/components/ClusterTable/ClusterTable.stories.tsx deleted file mode 100644 index 83405d30..00000000 --- a/components/ClusterTable/ClusterTable.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; - -import { ClusterTable } from './ClusterTable'; - -import { noop } from '@/utils/noop'; -import { mapClusterFromRaw } from '@/utils/mapClustersFromRaw'; -import { mockClusterResponse } from '@/tests/mocks/mockClusterResponse'; - -const { managementCluster } = mapClusterFromRaw(mockClusterResponse); - -const meta: Meta = { - component: ClusterTable, -}; - -export default meta; - -export const Default: StoryObj = { - args: { - onDeleteCluster: noop, - managementCluster, - }, -}; diff --git a/components/ClusterTable/ClusterTable.styled.ts b/components/ClusterTable/ClusterTable.styled.ts deleted file mode 100644 index bb2289a6..00000000 --- a/components/ClusterTable/ClusterTable.styled.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { styled as muiStyled } from '@mui/material/styles'; -import TableCell, { tableCellClasses } from '@mui/material/TableCell'; -import TableBody, { tableBodyClasses } from '@mui/material/TableBody'; -import TableRow, { tableRowClasses } from '@mui/material/TableRow'; -import IconButton from '@mui/material/IconButton'; -import Box from '@mui/material/Box'; -import Table from '@mui/material/Table'; -import { typographyClasses } from '@mui/material/Typography'; - -import Typography from '../Typography/Typography'; -import Tag from '../Tag/Tag'; - -import { - CHEFS_HAT, - PASTEL_LIGHT_BLUE, - ROCK_BLUE, - SALTBOX_BLUE, - VOLCANIC_SAND, -} from '@/constants/colors'; -import styled, { css } from '@/app/lib/styled-components'; - -export const Menu = styled(Box)` - position: absolute; - bottom: -40px; - left: -110px; - width: 160px; - background-color: white; - border: 1px solid ${CHEFS_HAT}; - border-radius: 8px; - box-shadow: 0px 2px 4px 0px rgba(100, 116, 139, 0.25); - z-index: 1; -`; - -export const StyledIconButton = styled(IconButton).withConfig({ - shouldForwardProp: (prop) => prop !== 'expanded', -})<{ expanded?: boolean }>` - svg { - color: ${ROCK_BLUE}; - transition: transform 0.3s ease; - } - - ${({ expanded }) => - expanded && - css` - svg { - transform: rotate(180deg); - } - `} -`; - -export const StyledTableBody = muiStyled(TableBody)(() => ({ - [`&.${tableBodyClasses.root}`]: { - borderRadius: '4px', - boxShadow: `0 0 0 2px ${PASTEL_LIGHT_BLUE}`, - }, -})); - -export const StyledTableCell = muiStyled(TableCell)<{ selected?: boolean }>(({ selected }) => ({ - [`&.${tableCellClasses.root}`]: { - border: 0, - backgroundColor: selected ? '#FAF5FF' : 'white', - }, -})); - -export const StyledHeaderCell = muiStyled(StyledTableCell)(() => ({ - [`&.${tableCellClasses.root}`]: { - backgroundColor: 'transparent', - }, -})); - -export const StyledTag = styled(Tag)` - width: fit-content; -`; - -export const StyledTableRow = muiStyled(TableRow)<{ selected?: boolean }>(() => ({ - [`&.${tableRowClasses.root}`]: { - border: 0, - height: 'fit-content', - }, - [`&.${tableRowClasses.root}.Mui-selected`]: { - backgroundColor: 'transparent', - }, - ['&:first-child td:first-child']: { - borderTopLeftRadius: '4px', - }, - ['&:first-child td:last-child']: { - borderTopRightRadius: '4px', - }, - ['&:last-child td:first-child']: { - borderBottomLeftRadius: '4px', - }, - ['&:last-child td:last-child']: { - borderBottomRightRadius: '4px', - }, -})); - -export const StyledTableHeading = muiStyled(Typography)(() => ({ - [`&.${typographyClasses.root}`]: { - color: SALTBOX_BLUE, - }, -})); - -export const StyledCellText = muiStyled(Typography)(() => ({ - [`&.${typographyClasses.root}`]: { - color: VOLCANIC_SAND, - fontWeight: 400, - }, -})); - -export const StyledTable = styled(Table)` - border-collapse: collapse; -`; diff --git a/components/ClusterTable/ClusterTable.tsx b/components/ClusterTable/ClusterTable.tsx deleted file mode 100644 index 4ca695a6..00000000 --- a/components/ClusterTable/ClusterTable.tsx +++ /dev/null @@ -1,358 +0,0 @@ -import React, { - useState, - FunctionComponent, - useMemo, - ComponentPropsWithRef, - useCallback, - MouseEvent, -} from 'react'; -import { ClickAwayListener } from '@mui/material'; -import TableHead from '@mui/material/TableHead'; -import IconButton from '@mui/material/IconButton'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemButton from '@mui/material/ListItemButton'; -import TableSortLabel from '@mui/material/TableSortLabel'; -import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; -import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; -import Image from 'next/image'; -import moment from 'moment'; - -import Tag from '../Tag/Tag'; -import Typography from '../Typography/Typography'; - -import { - StyledTableRow, - StyledTableCell, - StyledTag, - StyledTableBody, - StyledTableHeading, - StyledCellText, - Menu, - StyledHeaderCell, - StyledIconButton, - StyledTable, -} from './ClusterTable.styled'; - -import k3dLogo from '@/assets/k3d_logo.svg'; -import awsLogo from '@/assets/aws_logo.svg'; -import akamaiLogo from '@/assets/akamai_logo.svg'; -import civoLogo from '@/assets/civo_logo.svg'; -import digitalOceanLogo from '@/assets/digital_ocean_logo.svg'; -import vultrLogo from '@/assets/vultr_logo.svg'; -import googleCloudLogo from '@/assets/googleCloud.svg'; -import { CLUSTER_TAG_CONFIG } from '@/constants'; -import { DODGER_BLUE, FIRE_BRICK } from '@/constants/colors'; -import { - ManagementCluster, - ClusterStatus, - ClusterType, - Cluster, - DraftCluster, -} from '@/types/provision'; -import { ClusterCache, InstallationType } from '@/types/redux'; -import { noop } from '@/utils/noop'; -import { NestedKeyOf } from '@/types'; -import { descendingComparator } from '@/utils/descendingComparator'; -import useToggle from '@/hooks/useToggle'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const CLOUD_LOGO_OPTIONS: Record = { - [InstallationType.LOCAL]: k3dLogo, - [InstallationType.AKAMAI]: akamaiLogo, - [InstallationType.AWS]: awsLogo, - [InstallationType.CIVO]: civoLogo, - [InstallationType.DIGITAL_OCEAN]: digitalOceanLogo, - [InstallationType.VULTR]: vultrLogo, - [InstallationType.GOOGLE]: googleCloudLogo, -}; - -const FORMATTED_CLUSTER_TYPE: Record = { - [ClusterType.MANAGEMENT]: { nameLabel: 'management', typeLabel: 'Physical' }, - [ClusterType.WORKLOAD]: { nameLabel: 'worker', typeLabel: 'Physical' }, - [ClusterType.WORKLOAD_V_CLUSTER]: { nameLabel: 'worker', typeLabel: 'Virtual' }, -}; - -type ClusterRowProps = { - cluster: Cluster | DraftCluster; - expanded?: boolean; - showExpandButton?: boolean; - onExpanseClick?: () => void; - onDeleteCluster: (clusterName: string) => void; - onClusterRowSelected: (clusterName: string) => void; - selected?: boolean; -}; - -const ClusterRow: FunctionComponent = ({ - cluster, - expanded, - showExpandButton, - onExpanseClick = noop, - onDeleteCluster, - onClusterRowSelected, - selected, -}) => { - const { isOpen, close, toggle } = useToggle(); - - const { - clusterName, - type, - cloudProvider, - cloudRegion, - creationDate, - gitAuth: { gitUser } = {}, - status, - nodeCount, - environment, - } = cluster; - - const cloudLogoSrc = CLOUD_LOGO_OPTIONS[cloudProvider ?? InstallationType.LOCAL]; - const { iconLabel, iconType, bgColor } = CLUSTER_TAG_CONFIG[status ?? ClusterStatus.PROVISIONED]; - const { nameLabel, typeLabel } = FORMATTED_CLUSTER_TYPE[type ?? ClusterType.MANAGEMENT]; - - const highlighted = useMemo(() => selected || isOpen, [selected, isOpen]); - - const handleClick = useCallback( - (e: MouseEvent) => { - e.stopPropagation(); - toggle(); - }, - [toggle], - ); - - const handleListItemClick = useCallback( - (e: MouseEvent) => { - e.stopPropagation(); - onDeleteCluster(clusterName); - }, - [onDeleteCluster, clusterName], - ); - - return ( - onClusterRowSelected(clusterName)}> - - {type === ClusterType.MANAGEMENT && showExpandButton && ( - - - - )} - - - - {clusterName} - - - {nameLabel} - - - - {typeLabel} - - - - {environment && } - - - - {cloudProvider - - - {cloudRegion} - - - {type !== ClusterType.WORKLOAD_V_CLUSTER && ( - {nodeCount} - )} - - - {creationDate && ( - - {moment(+creationDate).format('DD MMM YYYY, HH:MM:SS')} - - )} - - - {gitUser} - - - - - - - - - {isOpen && ( - - - - - - - Delete cluster - - - - - - - )} - - - ); -}; - -type NestedKeyOfCluster = NestedKeyOf; - -type HeadCell = { - id: NestedKeyOfCluster; - label: string; -}; - -const headCells: HeadCell[] = [ - { - id: 'clusterName', - label: 'Name', - }, - { - id: 'type', - label: 'Type', - }, - { - id: 'environment', - label: 'Environment', - }, - { - id: 'cloudProvider', - label: 'Cloud', - }, - { - id: 'cloudRegion', - label: 'Region', - }, - { - id: 'nodeCount', - label: 'Nodes', - }, - { - id: 'creationDate', - label: 'Created', - }, - { - id: 'gitAuth.gitOwner', - label: 'Created by', - }, - { - id: 'status', - label: 'Status', - }, -]; -type Order = 'asc' | 'desc'; -interface ClusterTableHeadProps { - orderBy: NestedKeyOfCluster; - order: Order; - onSort: (orderBy: NestedKeyOfCluster) => void; -} - -const ClusterTableHead: FunctionComponent = ({ orderBy, order, onSort }) => { - return ( - - - - {headCells.map((cell) => ( - - onSort(cell.id)} - > - {cell.label} - - - ))} - - - ); -}; - -interface ClusterTableProps extends Omit, 'key'> { - managementCluster: ManagementCluster; - clusters: ClusterCache; - onDeleteCluster: (clusterName: string) => void; - selectedClusterName?: string; - onClusterRowSelected: (clusterName: string) => void; -} - -export const ClusterTable: FunctionComponent = ({ - managementCluster, - clusters, - onDeleteCluster, - selectedClusterName, - onClusterRowSelected, -}) => { - const [expanded, setExpanded] = useState(true); - const [orderBy, setOrderBy] = useState('clusterName'); - const [order, setOrder] = useState('asc'); - - const handleRequestSort = (property: NestedKeyOfCluster) => { - const isAsc = orderBy === property && order === 'asc'; - setOrder(isAsc ? 'desc' : 'asc'); - setOrderBy(property); - }; - - const filteredWorkloadClusters = useMemo(() => { - return Object.values(clusters) - .filter( - (cluster) => - cluster.status !== ClusterStatus.DELETED && cluster.type !== ClusterType.MANAGEMENT, - ) - .sort((a, b) => - order === 'asc' - ? -descendingComparator(a, b, orderBy) - : descendingComparator(a, b, orderBy), - ); - }, [clusters, order, orderBy]); - - return ( - - - - setExpanded(!expanded)} - selected={selectedClusterName === managementCluster.clusterName} - onClusterRowSelected={onClusterRowSelected} - /> - - {expanded && - filteredWorkloadClusters.map((cluster, index) => ( - - ))} - - - ); -}; - -export default ClusterTable; diff --git a/components/ClusterUsageTable/ClusterUsageTable.styled.ts b/components/ClusterUsageTable/ClusterUsageTable.styled.ts deleted file mode 100644 index a47e2ae6..00000000 --- a/components/ClusterUsageTable/ClusterUsageTable.styled.ts +++ /dev/null @@ -1,79 +0,0 @@ -'use client'; -import styled from 'styled-components'; -import { styled as muiStyled } from '@mui/material/styles'; -import TableCell, { tableCellClasses } from '@mui/material/TableCell'; -import Table from '@mui/material/Table'; -import Box from '@mui/material/Box'; -import TableRow from '@mui/material/TableRow'; -import TableBody, { tableBodyClasses } from '@mui/material/TableBody'; -import { typographyClasses } from '@mui/material/Typography'; - -import Row from '../Row/Row'; -import Typography from '../Typography/Typography'; -import { - CHEFS_DARK_HAT, - CHEFS_HAT, - CHILD_OF_LIGHT, - SALTBOX_BLUE, - VOLCANIC_SAND, -} from '../../constants/colors'; - -export const Menu = styled(Box)` - position: absolute; - top: 40px; - left: -130px; - width: 160px; - background-color: white; - border: 1px solid ${CHEFS_HAT}; - border-radius: 8px; - box-shadow: 0px 2px 4px 0px rgba(100, 116, 139, 0.25); - z-index: 1; -`; - -export const StyledTableBody = muiStyled(TableBody)(() => ({ - [`&.${tableBodyClasses.root}`]: {}, -})); - -export const StyledTableCell = muiStyled(TableCell)(() => ({ - [`&.${tableCellClasses.root}`]: { - borderBottom: `1px solid ${CHEFS_DARK_HAT}`, - backgroundColor: 'white', - }, -})); - -export const StyledHeaderCell = muiStyled(StyledTableCell)(() => ({ - [`&.${tableCellClasses.root}`]: { - backgroundColor: CHILD_OF_LIGHT, - }, -})); - -export const StyledTableRow = muiStyled(TableRow)(() => ({ - ['&:first-child th:first-child']: { - borderTopLeftRadius: '8px', - }, - ['&:first-child th:last-child']: { - borderTopRightRadius: '8px', - }, -})); - -export const StyledTableHeading = muiStyled(Typography)(() => ({ - [`&.${typographyClasses.root}`]: { - color: SALTBOX_BLUE, - }, -})); - -export const StyledCellText = muiStyled(Typography)(() => ({ - [`&.${typographyClasses.root}`]: { - color: VOLCANIC_SAND, - fontWeight: 400, - }, -})); - -export const StyledTable = styled(Table)` - border-collapse: collapse; - margin: 5px; - height: fit-content; - margin: 0; -`; - -export const StyledTableContainer = styled(Row)``; diff --git a/components/ClusterUsageTable/ClusterUsageTable.tsx b/components/ClusterUsageTable/ClusterUsageTable.tsx deleted file mode 100644 index 9b64a84c..00000000 --- a/components/ClusterUsageTable/ClusterUsageTable.tsx +++ /dev/null @@ -1,195 +0,0 @@ -'use client'; -import React, { useState, FunctionComponent, useMemo, ComponentPropsWithRef } from 'react'; -import TableHead from '@mui/material/TableHead'; -import TableSortLabel from '@mui/material/TableSortLabel'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -import moment from 'moment'; - -import Typography from '../Typography/Typography'; - -import { - StyledTableRow, - StyledTableCell, - StyledTableBody, - StyledTableHeading, - StyledCellText, - StyledHeaderCell, - StyledTableContainer, - StyledTable, -} from './ClusterUsageTable.styled'; - -import { ECHO_BLUE, VOLCANIC_SAND } from '@/constants/colors'; -import { descendingComparator } from '@/utils/descendingComparator'; -import { ClusterUsage } from '@/types/subscription'; - -interface ClusterUsageProps { - cluster: ClusterUsage; -} - -const ClusterUsageRow: FunctionComponent = ({ cluster }) => { - const { clusterName, clusterID, clusterType, createdAt, deletedAt, hours, total } = cluster; - - return ( - - - - {clusterName} - - - - - {clusterID} - - - - - {clusterType} - - - - - {moment(createdAt).format('DD MMM YYYY, HH:MM')} - {deletedAt ? ` - ${moment(deletedAt).format('DD MMM YYYY, HH:MM')}` : ' - Present'} - - - - - {hours} - - - - - {`$${total}`} - - - - ); -}; - -type ClusterUsageType = keyof ClusterUsage; - -type HeadCell = { - alignment?: 'inherit' | 'left' | 'center' | 'right' | 'justify'; - id: ClusterUsageType; - label: string; - width?: number; -}; - -const headCells: HeadCell[] = [ - { - id: 'clusterName', - label: 'Cluster Name', - }, - { - id: 'clusterID', - label: 'Cluster ID', - }, - { - id: 'clusterType', - label: 'Cluster Type', - }, - { - id: 'createdAt', - label: 'Duration', - }, - { - id: 'hours', - label: 'Hours', - alignment: 'right', - width: 100, - }, - { - id: 'total', - label: 'Total', - alignment: 'right', - width: 100, - }, -]; - -type Order = 'asc' | 'desc'; - -interface ClusterUsageTableHeadProps { - orderBy: ClusterUsageType; - order: Order; - onSort: (orderBy: ClusterUsageType) => void; -} - -const ClusterUsageTableHead: FunctionComponent = ({ - orderBy, - order, - onSort, -}) => { - return ( - - - {headCells.map(({ alignment, id, label, width }) => ( - - onSort(id)} - IconComponent={ArrowDropDownIcon} - sx={{ - '.MuiTableSortLabel-icon': { - fill: ECHO_BLUE, - }, - }} - > - {label} - - - ))} - - - ); -}; - -interface ClusterUsageTableProps extends Omit, 'key'> { - clusters: Array; - customRef?: React.Ref; -} - -export const ClusterUsageTable: FunctionComponent = ({ - clusters, - customRef, -}) => { - const [orderBy, setOrderBy] = useState('clusterType'); - const [order, setOrder] = useState('asc'); - - const handleRequestSort = (property: ClusterUsageType) => { - const isAsc = orderBy === property && order === 'asc'; - setOrder(isAsc ? 'desc' : 'asc'); - setOrderBy(property); - }; - - const sortedRecords = useMemo(() => { - return ( - clusters && - Object.values(clusters).sort((a, b) => - order === 'asc' - ? -descendingComparator(a, b, orderBy) - : descendingComparator(a, b, orderBy), - ) - ); - }, [clusters, order, orderBy]); - - return ( - - - - - {sortedRecords && - sortedRecords.map((record) => ( - - ))} - - - - ); -}; - -const ClusterUsageTableWithRef = React.forwardRef( - (props, ref) => , -); - -export default ClusterUsageTableWithRef; diff --git a/components/CreateEnvironmentMenu/CreateEnvironmentMenu.stories.tsx b/components/CreateEnvironmentMenu/CreateEnvironmentMenu.stories.tsx deleted file mode 100644 index 8f6695e7..00000000 --- a/components/CreateEnvironmentMenu/CreateEnvironmentMenu.stories.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; - -import { mockEnvironmentsResponse } from '../../tests/mocks/mockEnvironmentsResponse'; -import { createEnvMap } from '../../utils/createEnvMap'; - -import { CreateEnvironmentMenu } from './CreateEnvironmentMenu'; - -const mockEnvironments = createEnvMap(mockEnvironmentsResponse); - -const meta: Meta = { - component: CreateEnvironmentMenu, -}; - -export default meta; - -export const Default: StoryObj = { - args: { - previouslyCreatedEnvironments: mockEnvironments, - errorMessage: '', - }, -}; diff --git a/components/CreateEnvironmentMenu/CreateEnvironmentMenu.styled.ts b/components/CreateEnvironmentMenu/CreateEnvironmentMenu.styled.ts deleted file mode 100644 index 4831ea0a..00000000 --- a/components/CreateEnvironmentMenu/CreateEnvironmentMenu.styled.ts +++ /dev/null @@ -1,41 +0,0 @@ -'use client'; -import styled from 'styled-components'; - -import Row from '../Row/Row'; -import Column from '../Column/Column'; -import { LIGHT_GREY } from '../../constants/colors'; - -export const CloseButton = styled.button` - display: flex; - align-items: center; - background-color: transparent; - border: none; - padding: 0; - cursor: pointer; -`; - -export const Content = styled(Column)` - padding: 32px 24px; - gap: 24px; - max-height: 304px; - overflow-y: auto; -`; - -export const Footer = styled(Row)` - padding: 16px 24px; - border-top: 1px solid ${LIGHT_GREY}; - justify-content: flex-end; - gap: 16px; -`; - -export const Header = styled(Row)` - padding: 24px; - border-bottom: 1px solid ${LIGHT_GREY}; - justify-content: space-between; -`; - -export const Root = styled.form` - position: relative; - display: flex; - flex-direction: column; -`; diff --git a/components/CreateEnvironmentMenu/CreateEnvironmentMenu.tsx b/components/CreateEnvironmentMenu/CreateEnvironmentMenu.tsx deleted file mode 100644 index d84f0d4e..00000000 --- a/components/CreateEnvironmentMenu/CreateEnvironmentMenu.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React, { ComponentPropsWithoutRef, FormEvent, FunctionComponent, useCallback } from 'react'; -import { useForm } from 'react-hook-form'; -import CloseIcon from '@mui/icons-material/Close'; -import Alert from '@mui/material/Alert'; - -import Typography from '../Typography/Typography'; -import { TagColor } from '../Tag/Tag'; -import ControlledTextField from '../controlledFields/ControlledTextField/ControlledTextField'; -import ControlledTagSelect from '../controlledFields/ControlledTagSelect/ControlledTagSelect'; -import ControlledTextArea from '../controlledFields/ControlledTextArea/ControlledTextArea'; -import Button from '../Button/Button'; - -import { CloseButton, Content, Footer, Header, Root } from './CreateEnvironmentMenu.styled'; - -import { SALTBOX_BLUE } from '@/constants/colors'; -import { ClusterEnvironment } from '@/types/provision'; -import { EnvMap } from '@/redux/slices/environments.slice'; - -const ENVIRONMENT_MENU_COLOR_OPTIONS: TagColor[] = [ - 'gray', - 'cyan', - 'gold', - 'green', - 'light blue', - 'lime', - 'pink', - 'purple', -]; - -interface CreateEnvironmentMenuProps - extends Omit, 'onSubmit' | 'key'> { - onSubmit: (environment: ClusterEnvironment) => void; - onClose: () => void; - previouslyCreatedEnvironments?: EnvMap; - errorMessage?: string; - onErrorClose?: () => void; -} - -export const CreateEnvironmentMenu: FunctionComponent = ({ - onSubmit, - onClose, - previouslyCreatedEnvironments = {}, - errorMessage, - onErrorClose, - ...rest -}) => { - const { - control, - handleSubmit, - formState: { isValid, errors }, - } = useForm({ mode: 'onBlur', defaultValues: { color: 'gray' } }); - - const handleFormSubmit = useCallback( - (e: FormEvent) => { - // stop propogation of form event because this form is - // "nested" in the create workload cluster menu - e.stopPropagation(); - handleSubmit(onSubmit)(e); - }, - [handleSubmit, onSubmit], - ); - - return ( - -
- Create new environment - - - -
- - {errorMessage && ( - - {errorMessage} - - )} - - (name && !previouslyCreatedEnvironments[name]) || 'Environment name must be unique', - }} - control={control} - onErrorText={errors.name?.message} - /> - -
- -
-
-
- - -
-
- ); -}; diff --git a/components/DeleteCluster/DeleteCluster.stories.tsx b/components/DeleteCluster/DeleteCluster.stories.tsx deleted file mode 100644 index 01be63fa..00000000 --- a/components/DeleteCluster/DeleteCluster.stories.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React, { FunctionComponent, useState } from 'react'; -import { Meta, StoryObj } from '@storybook/react'; - -import DeleteCluster, { DeleteClusterProps } from './DeleteCluster'; - -import Button from '@/components/Button/Button'; -import { noop } from '@/utils/noop'; -import { Cluster, ClusterStatus, ClusterType } from '@/types/provision'; -import { InstallationType } from '@/types/redux'; -import { GitProvider } from '@/types'; - -const meta: Meta = { - title: 'Components/DeleteCluster', - component: DeleteCluster, -}; - -export default meta; - -const clusters: Cluster[] = [ - { - clusterId: '1', - clusterName: 'kuberfirst-mgmt', - type: ClusterType.MANAGEMENT, - cloudProvider: InstallationType.AWS, - cloudRegion: 'ap-southeast-1', - creationDate: '05 Apr 2023, 12:24:56', - status: ClusterStatus.PROVISIONED, - adminEmail: 'admin@mycompany.com', - gitProvider: GitProvider.GITHUB, - domainName: 'yourdomain.com', - dnsProvider: 'civo', - nodeCount: 2, - gitAuth: { - gitOwner: 'D-B-Hawk', - gitToken: 'secret', - gitUser: 'D-B-Hawk', - }, - }, - { - clusterId: '2', - clusterName: 'kuberfirst-worker-one', - type: ClusterType.WORKLOAD, - cloudProvider: InstallationType.CIVO, - cloudRegion: 'ap-southeast-1', - creationDate: '05 Apr 2023, 12:24:56', - status: ClusterStatus.ERROR, - adminEmail: 'admin@mycompany.com', - gitProvider: GitProvider.GITHUB, - domainName: 'yourdomain.com', - dnsProvider: 'civo', - nodeCount: 2, - gitAuth: { - gitOwner: 'D-B-Hawk', - gitToken: 'secret', - gitUser: 'D-B-Hawk', - }, - }, -]; - -const DeleteClusterWithHooks: FunctionComponent = (props) => { - const [open, setOpen] = useState(true); - return ( - <> - setOpen(false)} onDelete={noop} /> - - - ); -}; - -type Story = StoryObj; - -export const Management: Story = { - render: (args) => , -}; - -export const Worker: Story = { - render: (args) => , -}; diff --git a/components/DeleteCluster/DeleteCluster.styled.ts b/components/DeleteCluster/DeleteCluster.styled.ts deleted file mode 100644 index 196da142..00000000 --- a/components/DeleteCluster/DeleteCluster.styled.ts +++ /dev/null @@ -1,48 +0,0 @@ -'use client'; -import styled from 'styled-components'; -import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; - -import Column from '../Column/Column'; -import NextLinkComp from '../NextLink/NextLink'; -import Row from '../Row/Row'; -import Typography from '../Typography/Typography'; -import TextFieldWithRef from '../TextField/TextField'; - -import { LAUGHING_ORANGE, VOLCANIC_SAND } from '@/constants/colors'; - -export const Container = styled(Row)` - gap: 16px; -`; - -export const Content = styled(Column)` - gap: 8px; -`; - -export const ErrorIcon = styled(ErrorOutlineIcon).attrs({ htmlColor: LAUGHING_ORANGE })``; - -export const Footer = styled(Row)` - gap: 8px; - justify-content: flex-end; -`; - -export const GapContainer = styled(Column)` - gap: 32px; -`; - -export const MainMessage = styled(Typography).attrs({ variant: 'subtitle2' })` - color: ${VOLCANIC_SAND}; -`; -export const Text = styled(MainMessage).attrs({ variant: 'body2' })``; - -export const TextField = styled(TextFieldWithRef).attrs({ required: true })` - width: 100%; -`; - -export const NextLink = styled(NextLinkComp)` - a { - display: flex; - align-items: center; - gap: 8px; - font-size: 14px; - } -`; diff --git a/components/DeleteCluster/DeleteCluster.tsx b/components/DeleteCluster/DeleteCluster.tsx deleted file mode 100644 index 2e1cc4a5..00000000 --- a/components/DeleteCluster/DeleteCluster.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, { FunctionComponent, useState } from 'react'; -import LaunchOutlinedIcon from '@mui/icons-material/LaunchOutlined'; - -import Modal, { IModalProps } from '../Modal/Modal'; -import Button from '../Button/Button'; -import { Cluster, ClusterType, DraftCluster } from '../../types/provision'; -import CopyButton from '../CopyButton/CopyButton'; - -import { - Container, - Content, - ErrorIcon, - Footer, - GapContainer, - MainMessage, - NextLink, - Text, - TextField, -} from './DeleteCluster.styled'; - -import { DOCS_LINK } from '@/constants'; - -export interface DeleteClusterProps extends Omit { - cluster: Cluster | DraftCluster; - onDelete: () => void; -} - -const DeleteCluster: FunctionComponent = ({ cluster, onDelete, ...rest }) => { - const [matchingClusterName, setMatchingClusterName] = useState(''); - - const isManagementCluster = cluster.type === ClusterType.MANAGEMENT; - - return ( - - - - - - Delete {cluster.clusterName} ? - {isManagementCluster ? ( - <> - Deleting a management cluster is carried out via the CLI. - - Note:{' '} - {`deleting a management cluster will also delete all it's - corresponding worker clusters`} - - - <> - How to delete a management cluster - - - - - ) : ( - <> - - Are you sure you want to delete the cluster{' '} - ? - This action cannot be undone. - - - Note: You will still need to manually delete the cluster folder - from your gitops repository - - - )} - - {!isManagementCluster && ( - setMatchingClusterName(e.target.value)} - /> - )} -
- - -
-
-
-
- ); -}; - -export default DeleteCluster; diff --git a/components/DeleteEnvironment/DeleteEnvironment.stories.tsx b/components/DeleteEnvironment/DeleteEnvironment.stories.tsx deleted file mode 100644 index 82ab97d5..00000000 --- a/components/DeleteEnvironment/DeleteEnvironment.stories.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; - -import { mockEnvironmentsResponse } from '../../tests/mocks/mockEnvironmentsResponse'; -import { mapEnvironmentFromRaw } from '../../utils/mapEnvironmentFromRaw'; - -import DeleteEnvironment from './DeleteEnvironment'; - -const mockEnvironments = mockEnvironmentsResponse.map(mapEnvironmentFromRaw); - -const meta: Meta = { - title: 'Components/DeleteEnvironment', - component: DeleteEnvironment, -}; - -export default meta; - -type Story = StoryObj; - -export const Default: Story = { - args: { - environment: mockEnvironments[0], - boundToCluster: false, - isOpen: true, - }, -}; - -export const BoundEnvironment: Story = { - args: { - environment: mockEnvironments[0], - boundToCluster: true, - isOpen: true, - }, -}; diff --git a/components/DeleteEnvironment/DeleteEnvironment.styled.ts b/components/DeleteEnvironment/DeleteEnvironment.styled.ts deleted file mode 100644 index 4587b2a3..00000000 --- a/components/DeleteEnvironment/DeleteEnvironment.styled.ts +++ /dev/null @@ -1,35 +0,0 @@ -'use client'; -import styled from 'styled-components'; - -import Column from '../Column/Column'; -import NextLinkComp from '../NextLink/NextLink'; -import Row from '../Row/Row'; - -export const Content = styled(Column)` - gap: 8px; - margin-left: 38px; -`; - -export const CopyTextContainer = styled(Row)` - flex-wrap: wrap; - align-items: baseline; - gap: 3px; - margin-bottom: 24px; -`; - -export const Footer = styled.div` - display: flex; - gap: 8px; - justify-content: flex-end; - margin-top: 34px; -`; - -export const Header = styled.div` - display: flex; - gap: 12px; - margin-bottom: 8px; -`; - -export const NextLink = styled(NextLinkComp)` - display: inline; -`; diff --git a/components/DeleteEnvironment/DeleteEnvironment.tsx b/components/DeleteEnvironment/DeleteEnvironment.tsx deleted file mode 100644 index fbc11b61..00000000 --- a/components/DeleteEnvironment/DeleteEnvironment.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React, { FunctionComponent, PropsWithChildren, useState } from 'react'; -import Box from '@mui/material/Box'; -import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; - -import Typography from '../Typography/Typography'; -import TextFieldWithRef from '../TextField/TextField'; -import Modal from '../Modal/Modal'; -import { LAUGHING_ORANGE, TRUE_BLUE } from '../../constants/colors'; -import Button from '../Button/Button'; -import { ClusterEnvironment } from '../../types/provision'; -import CopyButton from '../CopyButton/CopyButton'; - -import { Content, CopyTextContainer, Footer, Header, NextLink } from './DeleteEnvironment.styled'; - -export interface DeleteEnvironmentProps extends PropsWithChildren { - environment: ClusterEnvironment; - boundToCluster: boolean; - isOpen: boolean; - onClose: () => void; - onDelete: () => void; - onCloseModal?: () => void; -} - -const DeleteEnvironment: FunctionComponent = ({ - environment, - boundToCluster, - isOpen, - onClose, - onDelete, - onCloseModal, -}) => { - const [matchingEnvName, setMatchingEnvName] = useState(''); - - return ( - - -
- - - {boundToCluster - ? 'Environment cannot be deleted while linked to cluster.' - : `Delete ${environment.name} environment?`} - -
- - {boundToCluster ? ( - <> - - To delete this environment first remove link it has with any clusters. - - - You can do this via the - Cluster management page. - - - ) : ( - <> - - Are you sure you want to delete the - - environment? This action cannot be undone. - - setMatchingEnvName(e.target.value)} - /> - - )} - -
- {!boundToCluster && ( - - )} - -
-
-
- ); -}; - -export default DeleteEnvironment; diff --git a/components/Drawer/Drawer.tsx b/components/Drawer/Drawer.tsx deleted file mode 100644 index 1290128f..00000000 --- a/components/Drawer/Drawer.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React, { FC, PropsWithChildren } from 'react'; -import DrawerMui, { DrawerProps } from '@mui/material/Drawer'; - -const Drawer: FC> = ({ children, ...delegated }) => { - return {children}; -}; - -export default Drawer; diff --git a/components/EnvironmentsTable/EnvironmentsTable.stories.tsx b/components/EnvironmentsTable/EnvironmentsTable.stories.tsx deleted file mode 100644 index a80bfd8c..00000000 --- a/components/EnvironmentsTable/EnvironmentsTable.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; - -import { mockEnvironmentsResponse } from '../../tests/mocks/mockEnvironmentsResponse'; -import { noop } from '../../utils/noop'; -import { createEnvMap } from '../../utils/createEnvMap'; - -import EnvironmentsTable from './EnvironmentsTable'; - -const mockEnvironments = createEnvMap(mockEnvironmentsResponse); - -const meta: Meta = { - component: EnvironmentsTable, -}; - -export default meta; - -export const Default: StoryObj = { - args: { - onDeleteEnvironment: noop, - environments: mockEnvironments, - }, -}; diff --git a/components/EnvironmentsTable/EnvironmentsTable.styled.ts b/components/EnvironmentsTable/EnvironmentsTable.styled.ts deleted file mode 100644 index 00d2632f..00000000 --- a/components/EnvironmentsTable/EnvironmentsTable.styled.ts +++ /dev/null @@ -1,80 +0,0 @@ -'use client'; -import styled from 'styled-components'; -import { styled as muiStyled } from '@mui/material/styles'; -import TableCell, { tableCellClasses } from '@mui/material/TableCell'; -import Table from '@mui/material/Table'; -import Box from '@mui/material/Box'; -import TableRow from '@mui/material/TableRow'; -import TableBody, { tableBodyClasses } from '@mui/material/TableBody'; -import { typographyClasses } from '@mui/material/Typography'; - -import Row from '../Row/Row'; -import Typography from '../Typography/Typography'; - -import { - CHEFS_DARK_HAT, - CHEFS_HAT, - CHILD_OF_LIGHT, - SALTBOX_BLUE, - VOLCANIC_SAND, -} from '@/constants/colors'; - -export const Menu = styled(Box)` - position: absolute; - top: 40px; - left: -130px; - width: 160px; - background-color: white; - border: 1px solid ${CHEFS_HAT}; - border-radius: 8px; - box-shadow: 0px 2px 4px 0px rgba(100, 116, 139, 0.25); - z-index: 1; -`; - -export const StyledTableBody = muiStyled(TableBody)(() => ({ - [`&.${tableBodyClasses.root}`]: {}, -})); - -export const StyledTableCell = muiStyled(TableCell)(() => ({ - [`&.${tableCellClasses.root}`]: { - borderBottom: `1px solid ${CHEFS_DARK_HAT}`, - backgroundColor: 'white', - }, -})); - -export const StyledHeaderCell = muiStyled(StyledTableCell)(() => ({ - [`&.${tableCellClasses.root}`]: { - backgroundColor: CHILD_OF_LIGHT, - }, -})); - -export const StyledTableRow = muiStyled(TableRow)(() => ({ - ['&:first-child th:first-child']: { - borderTopLeftRadius: '8px', - }, - ['&:first-child th:last-child']: { - borderTopRightRadius: '8px', - }, -})); - -export const StyledTableHeading = muiStyled(Typography)(() => ({ - [`&.${typographyClasses.root}`]: { - color: SALTBOX_BLUE, - }, -})); - -export const StyledCellText = muiStyled(Typography)(() => ({ - [`&.${typographyClasses.root}`]: { - color: VOLCANIC_SAND, - fontWeight: 400, - }, -})); - -export const StyledTable = styled(Table)` - border-collapse: collapse; - margin: 5px; - height: fit-content; - margin: 0 28px; -`; - -export const StyledTableContainer = styled(Row)``; diff --git a/components/EnvironmentsTable/EnvironmentsTable.tsx b/components/EnvironmentsTable/EnvironmentsTable.tsx deleted file mode 100644 index 47d51746..00000000 --- a/components/EnvironmentsTable/EnvironmentsTable.tsx +++ /dev/null @@ -1,231 +0,0 @@ -'use client'; -import React, { useState, FunctionComponent, useMemo, ComponentPropsWithRef } from 'react'; -import styled from 'styled-components'; -import TableHead from '@mui/material/TableHead'; -import ListItemButton from '@mui/material/ListItemButton'; -import ListItem from '@mui/material/ListItem'; -import List from '@mui/material/List'; -import TableSortLabel from '@mui/material/TableSortLabel'; -import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -import ClickAwayListener from '@mui/material/ClickAwayListener'; -import moment from 'moment'; - -import Typography from '../Typography/Typography'; -import Tag from '../Tag/Tag'; - -import { - StyledTableRow, - StyledTableCell, - StyledTableBody, - StyledTableHeading, - StyledCellText, - Menu, - StyledHeaderCell, - StyledTableContainer, - StyledTable, -} from './EnvironmentsTable.styled'; - -import { ECHO_BLUE, FIRE_BRICK, MAGNOLIA, PRIMARY, SALTBOX_BLUE } from '@/constants/colors'; -import { ClusterEnvironment } from '@/types/provision'; -import { descendingComparator } from '@/utils/descendingComparator'; -import { EnvMap } from '@/redux/slices/environments.slice'; -import useToggle from '@/hooks/useToggle'; - -const MyButton = styled.button<{ selected?: boolean }>` - display: flex; - justify-content: center; - align-items: center; - background-color: ${({ selected }) => (selected ? MAGNOLIA : 'inherit')}; - color: ${SALTBOX_BLUE}; - height: 36px; - width: 36px; - padding: 0; - border: none; - border-radius: 50%; - cursor: pointer; - - &:hover { - color: ${PRIMARY}; - background-color: ${MAGNOLIA}; - } -`; - -interface EnvironmentRowProps { - environment: ClusterEnvironment; - onDeleteEnvironment: (envId: string) => void; - // onEditEnvironment: () => void; -} - -const EnvironmentRow: FunctionComponent = ({ - environment, - onDeleteEnvironment, - // onEditEnvironment, -}) => { - const { id, name, description, color, creationDate } = environment; - const { isOpen, close, toggle } = useToggle(); - - return ( - - - - - - - - {description && {description}} - - - - {moment(+creationDate).format('DD MMM YYYY, HH:MM:SS')} - - - -
- - - - {isOpen && ( - - - - {/* - - Edit - - */} - - onDeleteEnvironment(id)}> - - Delete environment - - - - - - - )} -
-
-
- ); -}; - -type EnvKey = keyof ClusterEnvironment; - -type HeadCell = { - id: EnvKey; - label: string; -}; - -const headCells: HeadCell[] = [ - { - id: 'name', - label: 'Environment Name', - }, - { - id: 'description', - label: 'Description', - }, - { - id: 'creationDate', - label: 'Created', - }, -]; - -type Order = 'asc' | 'desc'; - -interface EnvironmentTableHeadProps { - orderBy: EnvKey; - order: Order; - onSort: (orderBy: EnvKey) => void; -} - -const EnvironmentTableHead: FunctionComponent = ({ - orderBy, - order, - onSort, -}) => { - return ( - - - {headCells.map((cell) => ( - - {/* Only allow sorting of table by env name or creation date */} - {cell.id !== 'description' ? ( - onSort(cell.id)} - IconComponent={ArrowDropDownIcon} - sx={{ - '.MuiTableSortLabel-icon': { - fill: ECHO_BLUE, - }, - }} - > - {cell.label} - - ) : ( - {cell.label} - )} - - ))} - - - - ); -}; - -interface EnvironmentsTableProps extends Omit, 'key'> { - environments: EnvMap; - onDeleteEnvironment: (envId: string) => void; - customRef?: React.Ref; - // onEditEnvironment: () => void; -} - -export const EnvironmentsTable: FunctionComponent = ({ - environments, - onDeleteEnvironment, - // onEditEnvironment, - customRef, -}) => { - const [orderBy, setOrderBy] = useState('creationDate'); - const [order, setOrder] = useState('asc'); - - const handleRequestSort = (property: EnvKey) => { - const isAsc = orderBy === property && order === 'asc'; - setOrder(isAsc ? 'desc' : 'asc'); - setOrderBy(property); - }; - - const sortedEnvironments = useMemo(() => { - return Object.values(environments).sort((a, b) => - order === 'asc' ? -descendingComparator(a, b, orderBy) : descendingComparator(a, b, orderBy), - ); - }, [environments, order, orderBy]); - - return ( - - - - - {sortedEnvironments.map((environment) => ( - - ))} - - - - ); -}; - -const EnvironmentTableWithRef = React.forwardRef( - (props, ref) => , -); - -export default EnvironmentTableWithRef; diff --git a/components/Flow/Controls/Controls.styled.ts b/components/Flow/Controls/Controls.styled.ts deleted file mode 100644 index 6edb59dc..00000000 --- a/components/Flow/Controls/Controls.styled.ts +++ /dev/null @@ -1,35 +0,0 @@ -'use client'; -import styled from 'styled-components'; -import { ControlButton } from 'reactflow'; - -import { PASTEL_LIGHT_BLUE, ROCK_BLUE } from '@/constants/colors'; - -export const StyledControlButton = styled(ControlButton)` - height: 36px; - width: 32px; - flex-shrink: 0; - background-color: white; - border: 1px solid ${PASTEL_LIGHT_BLUE}; - padding: 0; - - svg { - max-height: unset; - max-width: unset; - height: 20px; - width: 20px; - color: ${ROCK_BLUE}; - } -`; - -export const FitViewButton = styled(StyledControlButton)` - border-radius: 4px 0px 0px 4px; -`; - -export const ZoomInButton = styled(StyledControlButton)` - border-left: none; - border-right: none; -`; - -export const ZoomOutButton = styled(StyledControlButton)` - border-radius: 0px 4px 4px 0px; -`; diff --git a/components/Flow/Controls/Controls.tsx b/components/Flow/Controls/Controls.tsx deleted file mode 100644 index c20a9f22..00000000 --- a/components/Flow/Controls/Controls.tsx +++ /dev/null @@ -1,39 +0,0 @@ -'use client'; -import React, { FunctionComponent } from 'react'; -import { ControlProps, Controls } from 'reactflow'; -import styled from 'styled-components'; -import FullscreenIcon from '@mui/icons-material/Fullscreen'; -import ZoomInIcon from '@mui/icons-material/ZoomIn'; -import ZoomOutIcon from '@mui/icons-material/ZoomOut'; - -import { FitViewButton, ZoomInButton, ZoomOutButton } from './Controls.styled'; - -const CustomReactFlowControls: FunctionComponent = ({ - onFitView, - onZoomIn, - onZoomOut, - ...rest -}) => ( - - - - - - - - - - - -); - -export default styled(CustomReactFlowControls)` - border: none; - box-shadow: none; -`; diff --git a/components/Flow/Flow.stories.tsx b/components/Flow/Flow.stories.tsx deleted file mode 100644 index 0aa570be..00000000 --- a/components/Flow/Flow.stories.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; - -import { noop } from '../../utils/noop'; - -import { Flow } from './Flow'; - -const meta: Meta = { - component: Flow, -}; - -export default meta; - -export const Default: StoryObj = { - args: { - onNodeClick: noop, - }, -}; diff --git a/components/Flow/Flow.tsx b/components/Flow/Flow.tsx deleted file mode 100644 index 720521c1..00000000 --- a/components/Flow/Flow.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React, { type FunctionComponent, useEffect } from 'react'; -import ReactFlow, { NodeTypes, ReactFlowProvider, useReactFlow } from 'reactflow'; - -import { CustomGraphNode, GraphNode } from '../GraphNode/GraphNode'; - -import CustomReactFlowControls from './Controls/Controls'; - -import { generateNodesConfig } from '@/utils/reactFlow'; -import { useAppDispatch, useAppSelector } from '@/redux/store'; -import { - onConnect, - onEdgesChange, - onNodesChange, - selectNodeById, - setEdges, - setNodes, -} from '@/redux/slices/reactFlow.slice'; -import { RESERVED_DRAFT_CLUSTER_NAME } from '@/constants'; - -import 'reactflow/dist/style.css'; - -const nodeTypes: NodeTypes = { - custom: GraphNode, -}; - -interface GraphViewProps { - onNodeClick: (clusterName: string) => void; -} - -const GraphView: FunctionComponent = ({ onNodeClick }) => { - const { nodes, edges } = useAppSelector(({ reactFlow }) => reactFlow); - const { managementCluster, clusterMap, presentedClusterName } = useAppSelector(({ api }) => api); - - const dispatch = useAppDispatch(); - - const { setCenter, fitView, zoomIn, zoomOut } = useReactFlow(); - - useEffect(() => { - if (presentedClusterName) { - let selectedNode: CustomGraphNode | undefined; - // with clusterMap being indexed by clusterName - // if the clusterName is selected by name it will - // cause the selected styles to cease on change - // so opting to select cluster by id - if (presentedClusterName === RESERVED_DRAFT_CLUSTER_NAME) { - selectedNode = nodes.find((node) => node.id === presentedClusterName); - } else { - selectedNode = nodes.find((node) => node.data.clusterName === presentedClusterName); - } - if (selectedNode) { - const { position, width, height, id } = selectedNode; - - dispatch(selectNodeById(id)); - - setCenter(position.x + (width ?? 400), position.y + (height ?? 0), { - zoom: 1.2, - duration: 500, - }); - } - } else { - window.requestAnimationFrame(() => fitView({ duration: 500, padding: 0.2 })); - } - }, [presentedClusterName, nodes, setCenter, fitView, dispatch]); - - useEffect(() => { - if (managementCluster) { - const [nodes, edges] = generateNodesConfig(managementCluster, clusterMap); - dispatch(setNodes(nodes)); - dispatch(setEdges(edges)); - } - }, [managementCluster, clusterMap, dispatch]); - - return ( - dispatch(onNodesChange(changes))} - onEdgesChange={(changes) => dispatch(onEdgesChange(changes))} - onConnect={(connection) => dispatch(onConnect(connection))} - onNodeClick={(_, node) => onNodeClick(node.data.clusterName)} - nodeTypes={nodeTypes} - fitView - > - fitView({ duration: 300, padding: 0.2 })} - onZoomIn={() => zoomIn({ duration: 300 })} - onZoomOut={() => zoomOut({ duration: 300 })} - /> - - ); -}; - -export const Flow: FunctionComponent = (props) => ( - - - -); diff --git a/components/GitOpsAppModal/GitOpsAppModal.stories.tsx b/components/GitOpsAppModal/GitOpsAppModal.stories.tsx deleted file mode 100644 index d22de4c4..00000000 --- a/components/GitOpsAppModal/GitOpsAppModal.stories.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import { useForm } from 'react-hook-form'; -import { Meta, StoryObj } from '@storybook/react'; - -import Button from '../Button/Button'; - -import GitopsAppModal from './GitOpsAppModal'; - -import useModal from '@/hooks/useModal'; -import { noop } from '@/utils/noop'; -import { mockGitopsCatalogApp } from '@/tests/mocks/mockGitopsCatalogApp'; - -const meta: Meta = { - component: GitopsAppModal, -}; - -export default meta; - -const GitopsAppModalWithHooks = () => { - const { isOpen, openModal, closeModal } = useModal(true); - - const { - control, - formState: { isValid }, - } = useForm(); - - return ( -
- - -
- ); -}; - -export const Default: StoryObj = { - render: () => , -}; diff --git a/components/GitOpsAppModal/GitOpsAppModal.styled.ts b/components/GitOpsAppModal/GitOpsAppModal.styled.ts deleted file mode 100644 index 241111e4..00000000 --- a/components/GitOpsAppModal/GitOpsAppModal.styled.ts +++ /dev/null @@ -1,43 +0,0 @@ -'use client'; -import styled from 'styled-components'; -import CloseIcon from '@mui/icons-material/Close'; - -import Column from '../Column/Column'; -import Row from '../Row/Row'; - -export const Container = styled(Column)` - max-width: 630px; - min-width: 300px; - background-color: white; - border-radius: 8px; - box-shadow: 0px 2px 4px rgba(100, 116, 139, 0.1); -`; - -export const Content = styled(Column)` - gap: 24px; - padding: 32px 24px; - min-height: 148px; - max-height: 50vh; - overflow-y: scroll; -`; - -export const Close = styled(CloseIcon)` - cursor: pointer; - position: fixed; - top: 25px; - right: 25px; -`; - -export const Footer = styled(Row)` - gap: 16px; - justify-content: flex-end; - padding: 16px 24px; - border-top: 1px solid #e0e0e0; -`; - -export const Header = styled(Row)` - gap: 16px; - padding: 24px; - text-transform: capitalize; - border-bottom: 1px solid #e0e0e0; -`; diff --git a/components/GitOpsAppModal/GitOpsAppModal.tsx b/components/GitOpsAppModal/GitOpsAppModal.tsx deleted file mode 100644 index 7ca1e6b5..00000000 --- a/components/GitOpsAppModal/GitOpsAppModal.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React, { FunctionComponent } from 'react'; -import Image from 'next/image'; -import { Control } from 'react-hook-form'; - -import Modal from '../Modal/Modal'; -import Button from '../Button/Button'; -import Typography from '../Typography/Typography'; -import ControlledPassword from '../controlledFields/ControlledPassword'; -import ControlledTextField from '../controlledFields/ControlledTextField/ControlledTextField'; - -import { Container, Content, Close, Footer, Header } from './GitOpsAppModal.styled'; - -import { GitOpsCatalogApp } from '@/types/applications'; -import { BISCAY, SALTBOX_BLUE } from '@/constants/colors'; - -export interface GitopsAppModalProps extends GitOpsCatalogApp { - control: Control; - isOpen: boolean; - isValid: boolean; - closeModal: () => void; - onSubmit: (app: GitOpsCatalogApp) => void; -} - -const GitopsAppModal: FunctionComponent = ({ - control, - closeModal, - isOpen, - isValid, - name, - image_url, - config_keys, - secret_keys, - onSubmit, - ...rest -}) => { - const handleSubmit = () => { - onSubmit({ name, image_url, config_keys, secret_keys, ...rest }); - }; - - return ( - - -
- {name} - - {name} - - -
- - {secret_keys && - secret_keys.map(({ label, name }) => ( - - ))} - - {config_keys && - config_keys.map(({ label, name }) => ( - - ))} - -
- - -
-
-
- ); -}; - -export default GitopsAppModal; diff --git a/components/GitOpsCatalogCard/GitOpsCatalogCard.stories.tsx b/components/GitOpsCatalogCard/GitOpsCatalogCard.stories.tsx deleted file mode 100644 index 1dd1a9b5..00000000 --- a/components/GitOpsCatalogCard/GitOpsCatalogCard.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; - -import { mockGitopsCatalogApp } from '../../tests/mocks/mockGitopsCatalogApp'; - -import GitOpsCatalogCard from './GitOpsCatalogCard'; - -const meta: Meta = { - component: GitOpsCatalogCard, -}; - -export default meta; - -export const Default: StoryObj = { - args: { - ...mockGitopsCatalogApp, - isDeletable: false, - }, -}; diff --git a/components/GitOpsCatalogCard/GitOpsCatalogCard.styled.ts b/components/GitOpsCatalogCard/GitOpsCatalogCard.styled.ts deleted file mode 100644 index 92cd887f..00000000 --- a/components/GitOpsCatalogCard/GitOpsCatalogCard.styled.ts +++ /dev/null @@ -1,71 +0,0 @@ -'use client'; -import styled from 'styled-components'; -import { styled as muiStyled } from '@mui/material'; - -import Typography from '../Typography/Typography'; -import Row from '../Row/Row'; -import Column from '../Column/Column'; - -import { textTruncate } from '@/utils/theme'; -import { CHEFS_HAT, DR_WHITE, VOLCANIC_SAND } from '@/constants/colors'; - -export const App = styled(Row)` - align-items: center; - gap: 16px; -`; - -export const Body = styled(Column)` - height: calc(100% - 30px); - justify-content: space-between; -`; - -export const Card = styled.div` - background-color: ${({ theme }) => theme.colors.white}; - border: 1px solid ${({ theme }) => theme.colors.pastelLightBlue}; - border-radius: 12px; - height: 194px; - padding: 24px; - width: 372px; -`; - -export const Category = styled(Row)``; - -export const Description = styled(Typography)<{ excludeTruncate: boolean }>` - color: ${({ theme }) => theme.colors.saltboxBlue}; - margin-top: 8px !important; - margin-bottom: 16px !important; - overflow: hidden; - text-overflow: ellipsis; - - ${({ excludeTruncate }) => !excludeTruncate && textTruncate(3)} - - & a { - color: ${({ theme }) => theme.colors.primary}; - text-decoration: none; - } - - & a:hover { - text-decoration: underline; - } -`; - -export const DisplayName = muiStyled(Typography)(() => ({ - textTransform: 'capitalize', - fontWeight: 600, - color: VOLCANIC_SAND, -})); - -export const Header = styled(Row)` - align-items: center; - justify-content: space-between; - gap: 16px; -`; - -export const Installing = styled(Row)` - background-color: ${DR_WHITE}; - border: 1px solid ${CHEFS_HAT}; - gap: 16px; - height: 40px; - padding: 16px; - width: calc(100% - 32px); -`; diff --git a/components/GitOpsCatalogCard/GitOpsCatalogCard.tsx b/components/GitOpsCatalogCard/GitOpsCatalogCard.tsx deleted file mode 100644 index 511193c6..00000000 --- a/components/GitOpsCatalogCard/GitOpsCatalogCard.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import React, { FunctionComponent, PropsWithChildren, useMemo } from 'react'; -import Image from 'next/image'; -import Box from '@mui/material/Box'; -import CircularProgress from '@mui/material/CircularProgress'; - -import Tag, { TagColor } from '../Tag/Tag'; -import Tooltip from '../Tooltip/Tooltip'; -import Button from '../Button/Button'; -import Typography from '../Typography/Typography'; - -import { - App, - Body, - Card, - Category, - Description, - DisplayName, - Header, - Installing, -} from './GitOpsCatalogCard.styled'; - -import { AppCategory, GitOpsCatalogApp } from '@/types/applications'; -import { VOLCANIC_SAND } from '@/constants/colors'; - -export const CATEGORY_COLOR_CONFIG: Record = { - [AppCategory.APP_MANAGEMENT]: 'purple', - [AppCategory.ARCHITECTURE]: 'pink', - [AppCategory.CI_CD]: 'gold', - [AppCategory.DATABASE]: 'indigo', - [AppCategory.FIN_OPS]: 'light blue', - [AppCategory.INFRASTRUCTURE]: 'gray', - [AppCategory.MONITORING]: 'emerald', - [AppCategory.OBSERVABIILITY]: 'light-orange', - [AppCategory.SECURITY]: 'dark-sky-blue', - [AppCategory.STORAGE]: 'green', - [AppCategory.TESTING]: 'lime', - [AppCategory.QUEUEING]: 'light blue', - [AppCategory.KUBESHOP]: 'light blue', - [AppCategory.APPLICATIONS]: 'light blue', -}; - -export type GitOpsCatalogCardProps = PropsWithChildren & { - isInstalling?: boolean; - onClick?: () => void; - showSubmitButton?: boolean; - isDeletable?: boolean; - isDisabled?: boolean; - excludeTruncate?: boolean; -}; - -const GitOpsCatalogCard: FunctionComponent = ({ - display_name, - category, - image_url, - description, - excludeTruncate = false, - isInstalling, - onClick, - showSubmitButton = true, - isDeletable = false, - isDisabled = false, - children, -}) => { - const tagColor = CATEGORY_COLOR_CONFIG[category ?? AppCategory.APP_MANAGEMENT] ?? {}; - const showTooltip = useMemo( - () => (description ? description.length > 167 : false), - [description], - ); - - return ( - -
- - {display_name} - {display_name} - - {category && ( - - - - )} -
- - {showTooltip ? ( - - {description || children} - - ) : ( - - {description || children} - - )} - {showSubmitButton && !isInstalling && ( - - )} - {isInstalling && ( - - - - - - Please wait a few moments while we finish installing your app. - - - )} - -
- ); -}; - -export default GitOpsCatalogCard; diff --git a/components/GraphNode/GraphNode.styled.ts b/components/GraphNode/GraphNode.styled.ts deleted file mode 100644 index a1de0f58..00000000 --- a/components/GraphNode/GraphNode.styled.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { styled as muiStyled } from '@mui/material/styles'; -import { typographyClasses } from '@mui/material/Typography'; -import { Handle } from 'reactflow'; -import Image from 'next/image'; - -import Typography from '../Typography/Typography'; -import Column from '../Column/Column'; -import Row from '../Row/Row'; -import Tag from '../Tag/Tag'; - -import { BLUE_REFLECTION, EXCLUSIVE_PLUM, VOLCANIC_SAND } from '@/constants/colors'; -import styled, { css } from '@/app/lib/styled-components'; - -export const StatusTag = styled(Tag)` - position: absolute; - top: 14px; - right: 17px; -`; -export const EnvironmentTag = styled(Tag)` - margin-top: 4px; -`; - -export const LeftPanel = styled(Column)` - margin-left: 83px; - height: 100%; - justify-content: center; - gap: 4px; -`; - -export const Img = styled(Image)` - position: absolute; - top: 0; - bottom: 0; - margin: auto 0; - left: -50px; -`; - -export const Container = styled(Row)<{ selected: boolean; managementCluster: boolean }>` - height: 126px; - width: 360px; - background-color: white; - border: 2px solid ${BLUE_REFLECTION}; - border-radius: 8px; - align-items: center; - color: ${VOLCANIC_SAND}; - - ${({ selected }) => - selected && - css` - filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25)); - `} - - ${({ managementCluster }) => - managementCluster && - css` - height: 96px; - border-radius: 0 8px 8px 0; - - ${LeftPanel} { - margin-left: 67px; - } - - ${StatusTag} { - top: 16px; - right: 33px; - } - - ${Img} { - height: 103px; - width: 103px; - } - `} -`; - -export const Nodes = muiStyled(Typography)(() => ({ - [`&.${typographyClasses.root}`]: { - fontSize: '11px', - fontWeight: 400, - color: EXCLUSIVE_PLUM, - lineHeight: '16px', - letterSpacing: '0.5px', - }, -})); -export const CloudProvider = muiStyled(Typography)(() => ({ - [`&.${typographyClasses.root}`]: { - fontSize: '12px', - fontWeight: 400, - lineHeight: '16px', - letterSpacing: '0.4px', - textTransform: 'uppercase', - }, -})); - -export const LabelContainer = styled(Row)` - align-items: center; - gap: 4px; -`; - -export const MainContainerInfo = styled(Row)` - height: 100%; - width: 100%; -`; - -export const NodeHandle = styled(Handle)<{ bgColor: string }>` - right: -8px; - height: 16px; - width: 16px; - border: none; - background-color: ${({ bgColor }) => bgColor}; -`; - -export const ClusterName = muiStyled(Typography)(() => ({ - [`&.${typographyClasses.root}`]: { - fontSize: '12px', - fontWeight: 600, - lineHeight: '16px', - letterSpacing: '0.4px', - textWrap: 'nowrap', - width: '196px', - marginBottom: '4px', - }, -})); diff --git a/components/GraphNode/GraphNode.tsx b/components/GraphNode/GraphNode.tsx deleted file mode 100644 index e6ebb717..00000000 --- a/components/GraphNode/GraphNode.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import React, { FunctionComponent, useMemo } from 'react'; -import { Node, NodeProps, Position, HandleType } from 'reactflow'; - -import Typography from '../Typography/Typography'; - -import { - Container, - CloudProvider, - LabelContainer, - MainContainerInfo, - NodeHandle, - ClusterName, - StatusTag, - EnvironmentTag, - LeftPanel, - Nodes, - Img, -} from './GraphNode.styled'; - -import managementCluster from '@/assets/managementIcon.svg'; -import vCluster from '@/assets/vCluster.svg'; -import vClusterAvailable from '@/assets/vClusterAvailable.svg'; -import cluster from '@/assets/cluster.svg'; -import clusterAvailable from '@/assets/clusterAvailable.svg'; -import { BUBBLE_GUM_BABY_GIRL } from '@/constants/colors'; -import { CLUSTER_TAG_CONFIG, RESERVED_DRAFT_CLUSTER_NAME } from '@/constants'; -import { Cluster, ClusterStatus, ClusterType, DraftCluster } from '@/types/provision'; - -const GRAPH_NODE_CONFIG: Record< - ClusterType, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - { handle: HandleType; position: Position; iconSrc: any } -> = { - [ClusterType.MANAGEMENT]: { - handle: 'source', - position: Position.Right, - iconSrc: managementCluster, - }, - [ClusterType.WORKLOAD]: { - handle: 'target', - position: Position.Left, - iconSrc: cluster, - }, - [ClusterType.WORKLOAD_V_CLUSTER]: { - handle: 'target', - position: Position.Left, - iconSrc: vCluster, - }, -}; - -export type CustomGraphNode = Node; - -export const GraphNode: FunctionComponent> = ({ - data, - isConnectable, - selected, -}) => { - const { - clusterId, - status, - type, - clusterName, - cloudProvider, - cloudRegion, - nodeCount, - environment, - } = data ?? {}; - - const { iconLabel, iconType, bgColor } = CLUSTER_TAG_CONFIG[status ?? ClusterStatus.PROVISIONED]; - const { handle, position, iconSrc } = GRAPH_NODE_CONFIG[type ?? ClusterType.WORKLOAD]; - - const draftNode = useMemo(() => clusterId === RESERVED_DRAFT_CLUSTER_NAME, [clusterId]); - const managementCluster = useMemo(() => type === ClusterType.MANAGEMENT, [type]); - - const imageSrc = useMemo(() => { - if (status === ClusterStatus.PROVISIONED && !draftNode) { - if (type === ClusterType.WORKLOAD) { - return clusterAvailable; - } else if (type === ClusterType.WORKLOAD_V_CLUSTER) { - return vClusterAvailable; - } - } - return iconSrc; - }, [iconSrc, status, type, draftNode]); - - return ( - - - - {clusterName} - {cloudProvider && cloudRegion && ( - - {cloudProvider}: - {cloudRegion} - - )} - {!!nodeCount && type !== ClusterType.WORKLOAD_V_CLUSTER && ( - - NODES: - {nodeCount} - - )} - {environment && } - - - - - cluster - - ); -}; diff --git a/components/HeadsUpNotification/HeadsUpNotification.stories.tsx b/components/HeadsUpNotification/HeadsUpNotification.stories.tsx deleted file mode 100644 index efe31eb2..00000000 --- a/components/HeadsUpNotification/HeadsUpNotification.stories.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; - -import HeadsUpNotification from './HeadsUpNotification'; - -const meta: Meta = { - title: 'Components/HeadsUpNotification', - component: HeadsUpNotification, -}; - -export default meta; - -export const Default: StoryObj = { - args: { - style: { margin: 50 }, - }, -}; diff --git a/components/HeadsUpNotification/HeadsUpNotification.styled.ts b/components/HeadsUpNotification/HeadsUpNotification.styled.ts deleted file mode 100644 index 52eb5dc7..00000000 --- a/components/HeadsUpNotification/HeadsUpNotification.styled.ts +++ /dev/null @@ -1,60 +0,0 @@ -'use client'; -import styled from 'styled-components'; -import { styled as muiStyled } from '@mui/material/styles'; -import { typographyClasses } from '@mui/material/Typography'; - -import Row from '../Row/Row'; -import Column from '../Column/Column'; -import Typography from '../Typography/Typography'; - -import { ECHO_BLUE, MOONLESS_MYTERY } from '@/constants/colors'; - -export const CloseButton = styled.button` - border: none; - background-color: transparent; - padding: 8px; - align-self: start; - cursor: pointer; - margin-left: 4px; - - & svg { - height: 16px; - width: 16px; - color: ${ECHO_BLUE}; - } -`; - -export const HeadsUp = muiStyled(Typography)(() => ({ - [`&.${typographyClasses.root}`]: { - color: 'white', - }, -})); - -export const Info = styled(Column)` - width: 100%; - gap: 4px; -`; - -export const LogoContainer = styled(Row)` - justify-content: center; - align-items: center; - height: 56px; - width: 56px; - border-radius: 16px; - background: linear-gradient(90deg, #6f37ae 40.63%, #7aa5e2 100%, #7aa5e2 100%); - flex-shrink: 0; - align-self: center; -`; - -export const Message = muiStyled(Typography)(() => ({ - [`&.${typographyClasses.root}`]: { - color: ECHO_BLUE, - }, -})); - -export const Root = styled(Row)` - padding: 16px; - border-radius: 12px; - background-color: ${MOONLESS_MYTERY}; - gap: 16px; -`; diff --git a/components/HeadsUpNotification/HeadsUpNotification.tsx b/components/HeadsUpNotification/HeadsUpNotification.tsx deleted file mode 100644 index f04abeca..00000000 --- a/components/HeadsUpNotification/HeadsUpNotification.tsx +++ /dev/null @@ -1,42 +0,0 @@ -'use client'; -import React, { ComponentPropsWithoutRef, FunctionComponent } from 'react'; -import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; -import Image from 'next/image'; -import styled from 'styled-components'; - -import { - CloseButton, - HeadsUp, - Info, - LogoContainer, - Message, - Root, -} from './HeadsUpNotification.styled'; - -import lightningBolt from '@/assets/lightningBolt.svg'; - -interface HeadsUpNotificationProps extends Omit, 'key'> { - onClose: () => void; -} - -const HeadsUpNotification: FunctionComponent = ({ onClose, ...rest }) => { - return ( - - - lightning bolt - - - A heads up! - - Please note that provisioning physical clusters via the UI may be subject to paywall - restrictions in the future. We’ll give you plenty of notice when that happens. - - - - - - - ); -}; - -export default styled(HeadsUpNotification)``; diff --git a/components/JoyRideTooltip/JoyRideTooltip.styled.ts b/components/JoyRideTooltip/JoyRideTooltip.styled.ts deleted file mode 100644 index 3753780c..00000000 --- a/components/JoyRideTooltip/JoyRideTooltip.styled.ts +++ /dev/null @@ -1,33 +0,0 @@ -import styled from 'styled-components'; -import { styled as muiStyled } from '@mui/material'; - -import Column from '../Column/Column'; -import Row from '../Row/Row'; -import Typography from '../Typography/Typography'; - -import { ECHO_BLUE, MIDNIGHT_EXPRESS, WHITE } from '@/constants/colors'; - -export const TooltipBody = styled(Column)` - background-color: ${MIDNIGHT_EXPRESS}; - border-radius: 12px; - padding: 24px; - gap: 12px; - width: 372px; -`; - -export const TooltipContent = muiStyled(Typography)(() => ({ - color: `${WHITE}`, - fontWeight: 400, -})); - -export const TooltipFooter = styled(Row)``; - -export const TooltipTitle = styled.p` - font-family: 'Inter', sans-serif; - font-style: normal; - font-size: 14px; - font-weight: 500; - line-height: 20px; - color: ${ECHO_BLUE}; - margin: 0; -`; diff --git a/components/JoyRideTooltip/JoyRideTooltip.tsx b/components/JoyRideTooltip/JoyRideTooltip.tsx deleted file mode 100644 index 91f0b607..00000000 --- a/components/JoyRideTooltip/JoyRideTooltip.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { FunctionComponent } from 'react'; -import { TooltipRenderProps } from 'react-joyride'; - -import Button from '../Button/Button'; - -import { TooltipBody, TooltipContent, TooltipFooter, TooltipTitle } from './JoyRideTooltip.styled'; - -const JoyrideTooltip: FunctionComponent = ({ - continuous, - step, - closeProps, - primaryProps, - tooltipProps, - isLastStep, -}) => ( - - {step.title && {step.title}} - {step.content} - - {continuous ? ( - - ) : ( - - )} - - -); - -export default JoyrideTooltip; diff --git a/components/KubeConfigModal/KubeConfigModal.stories.tsx b/components/KubeConfigModal/KubeConfigModal.stories.tsx deleted file mode 100644 index 79f38ab7..00000000 --- a/components/KubeConfigModal/KubeConfigModal.stories.tsx +++ /dev/null @@ -1,46 +0,0 @@ -'use client'; -import React, { FunctionComponent } from 'react'; -import { Meta, StoryObj } from '@storybook/react'; - -import KubeConfigModal, { KubeConfigModalProps } from './KubeConfigModal'; - -import Button from '@/components/Button/Button'; -import useModal from '@/hooks/useModal'; - -const meta: Meta = { - component: KubeConfigModal, -}; - -export default meta; - -const KubeConfigModalWithHooks: FunctionComponent = ({ - command, - commandDocLink, -}) => { - const { isOpen, openModal, closeModal } = useModal(false); - - return ( - <> - - - - ); -}; - -export const Default: StoryObj = { - args: { - command: - 'gcloud container clusters get-credentials NAME [--internal-ip] [--location=LOCATION | --region=REGION | --zone=ZONE, -z ZONE] [GCLOUD_WIDE_FLAG …]', - commandDocLink: - 'https://cloud.google.com/sdk/gcloud/reference/container/clusters/get-credentials', - }, - render: (args) => , -}; diff --git a/components/KubeConfigModal/KubeConfigModal.styled.ts b/components/KubeConfigModal/KubeConfigModal.styled.ts deleted file mode 100644 index e19bbd7c..00000000 --- a/components/KubeConfigModal/KubeConfigModal.styled.ts +++ /dev/null @@ -1,38 +0,0 @@ -import styled from 'styled-components'; -import { styled as muiStyled } from '@mui/material'; -import { ErrorOutlineOutlined } from '@mui/icons-material'; - -import Row from '../Row/Row'; -import Column from '../Column/Column'; -import Typography from '../Typography/Typography'; - -import { PRIMARY, TRUE_BLUE, VOLCANIC_SAND } from '@/constants/colors'; - -export const ButtonContainer = styled(Row)` - justify-content: flex-end; - margin-top: 18px; -`; - -export const Content = styled(Column)` - width: 100%; - gap: 8px; -`; - -export const ExclamationIcon = muiStyled(ErrorOutlineOutlined)(() => ({ - height: '24px', - width: '24px', - fill: TRUE_BLUE, -})); - -export const ExternalLink = styled.a` - text-decoration: none; - color: ${PRIMARY}; -`; - -export const Main = styled(Row)` - gap: 16px; -`; - -export const StyledTypography = muiStyled(Typography)(() => ({ - color: VOLCANIC_SAND, -})); diff --git a/components/KubeConfigModal/KubeConfigModal.tsx b/components/KubeConfigModal/KubeConfigModal.tsx deleted file mode 100644 index f779e8cf..00000000 --- a/components/KubeConfigModal/KubeConfigModal.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { FunctionComponent } from 'react'; - -import Modal, { IModalProps } from '../Modal/Modal'; -import Column from '../Column/Column'; -import CopyCommand, { CopyCommandProps } from '../CopyCommand/CopyCommand'; -import Button from '../Button/Button'; - -import { - ButtonContainer, - Content, - ExclamationIcon, - ExternalLink, - Main, - StyledTypography, -} from './KubeConfigModal.styled'; - -export interface KubeConfigModalProps extends Omit, CopyCommandProps { - onAcceptance: () => void; - commandDocLink: string; -} - -const KubeConfigModal: FunctionComponent = ({ - command, - onAcceptance, - commandDocLink, - ...rest -}) => { - return ( - -
- {/* Exclamation Icon */} - - - - {/* Main Content */} - - - Access your kubeconfig file by using the following command: - - - You can find more information about accessing your clusters kubeconfig file{' '} - - here. - - - - - - - -
-
- ); -}; - -export default KubeConfigModal; diff --git a/components/Navigation/Navigation.tsx b/components/Navigation/Navigation.tsx index 854cdf44..3fd8c48d 100644 --- a/components/Navigation/Navigation.tsx +++ b/components/Navigation/Navigation.tsx @@ -1,6 +1,5 @@ import React, { FunctionComponent, ReactNode, useMemo } from 'react'; import Image from 'next/image'; -import VideogameAssetOutlinedIcon from '@mui/icons-material/VideogameAssetOutlined'; import { useRouter } from 'next/navigation'; import groupBy from 'lodash/groupBy'; import { sortBy } from 'lodash'; @@ -20,7 +19,6 @@ import { noop } from '@/utils/noop'; import { ECHO_BLUE } from '@/constants/colors'; import Ray from '@/assets/ray.svg'; import TitleLogo from '@/assets/title.svg'; -import Youtube from '@/assets/youtube.svg'; import { Route } from '@/constants'; export interface FooterItem { @@ -34,7 +32,6 @@ export interface NavigationProps { handleIsActiveItem: (path: string) => boolean; handleOpenContent: typeof noop; handleOpenGame: typeof noop; - isSubscriptionEnabled: boolean; kubefirstVersion?: string; routes: Array<{ group?: string; @@ -50,9 +47,6 @@ export interface NavigationProps { const Navigation: FunctionComponent = ({ domLoaded, handleIsActiveItem, - handleOpenContent, - handleOpenGame, - isSubscriptionEnabled, kubefirstVersion, routes, footerItems, @@ -120,20 +114,6 @@ const Navigation: FunctionComponent = ({ icon={icon} /> ))} - {!isSubscriptionEnabled && ( - <> - } - onMenuItemClick={handleOpenContent} - /> - } - onMenuItemClick={handleOpenGame} - /> - - )} ); diff --git a/components/Pricing/Pricing.styled.ts b/components/Pricing/Pricing.styled.ts deleted file mode 100644 index c606dc01..00000000 --- a/components/Pricing/Pricing.styled.ts +++ /dev/null @@ -1,23 +0,0 @@ -import styled from 'styled-components'; - -import Column from '../Column/Column'; - -export const Container = styled.div<{ isActive?: boolean }>` - border: 1px solid ${({ theme }) => theme.colors.pastelLightBlue}; - border-radius: 12px; - height: 650px; - padding: 24px; - width: 219px; - - ${({ isActive, theme }) => - isActive && - ` - border: 4px solid ${theme.colors.primary}; - `} -`; - -export const Features = styled(Column)``; - -export const PriceImage = styled.img` - object-fit: cover; -`; diff --git a/components/Pricing/Pricing.tsx b/components/Pricing/Pricing.tsx deleted file mode 100644 index 437ed5af..00000000 --- a/components/Pricing/Pricing.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React, { FunctionComponent, useMemo } from 'react'; -import Image from 'next/image'; - -import Typography from '../Typography/Typography'; -import Button from '../Button/Button'; - -import { Container, Features, PriceImage } from './Pricing.styled'; - -import { Plan } from '@/types/plan'; -import CheckIcon from '@/assets/check.png'; -import { BISCAY, SALTBOX_BLUE } from '@/constants/colors'; - -export interface PricingProps { - isActive?: boolean; - onClick: () => void; - plan: Plan; - hideButton?: boolean; -} - -const Pricing: FunctionComponent = ({ hideButton, isActive, onClick, plan }) => { - const { name, description, images, metadata, features } = plan; - - const buttonName = metadata && metadata['button']; - const featuresTitle = metadata && metadata['featuresTitle']; - const comingSoonTitle = metadata && metadata['comingSoonTitle']; - const priceLabel = metadata && metadata['priceLabel']; - - const comingSoonFeatures = useMemo( - () => features.filter(({ name }) => name.includes('SOON')), - [features], - ); - - const availableFeatures = useMemo( - () => features.filter(({ name }) => !name.includes('SOON')), - [features], - ); - - return ( - - - - {name} - - - {description} - - - {priceLabel} - - {!hideButton && ( - - )} - {featuresTitle && ( - - {featuresTitle} - - )} - - {availableFeatures.map((feature) => { - return ( - - check-icon - {feature.name.includes(':') ? feature.name.split(':')[1] : feature.name} - - ); - })} - {comingSoonTitle && comingSoonFeatures.length && ( - - {comingSoonTitle} - - )} - {comingSoonFeatures && - comingSoonFeatures.map((feature) => { - return ( - - check-icon - {feature.name.includes(':') ? feature.name.split(':')[1] : feature.name} - - ); - })} - - - ); -}; - -export default Pricing; diff --git a/components/TourModal/TourModal.stories.tsx b/components/TourModal/TourModal.stories.tsx deleted file mode 100644 index 3914f4bc..00000000 --- a/components/TourModal/TourModal.stories.tsx +++ /dev/null @@ -1,74 +0,0 @@ -'use client'; -import React, { useState } from 'react'; -import { Meta, StoryObj } from '@storybook/react'; -import Joyride, { Step } from 'react-joyride'; -import styled from 'styled-components'; -import Image from 'next/image'; - -import TourModal from './TourModal'; - -import Button from '@/components/Button/Button'; -import useModal from '@/hooks/useModal'; -import kubefirstRay from '@/assets/ray.svg'; - -const meta: Meta = { - component: TourModal, -}; - -export default meta; - -export const K1Ray = styled(Image).attrs({ - src: kubefirstRay, - alt: 'Kubefirst k ray', -})` - height: 162px; - width: 192px; - margin: 24px 0; - border: 1px dashed blue; -`; - -const TourModalWithHooks = () => { - const { isOpen, openModal, closeModal } = useModal(false); - const [startTour, setStartTour] = useState(false); - const [steps] = useState([ - { - target: '.kray', - content: 'My awesome first step', - disableBeacon: true, - }, - { - target: '.other', - content: 'My awesome second step', - disableBeacon: true, - }, - { - target: '#button', - content: 'check out this button', - disableBeacon: true, - spotlightPadding: 20, - }, - ]); - - return ( -
- - - { - closeModal(); - setStartTour(true); - }} - styleOverrides={{ width: '500px', padding: 0 }} - /> -
- ); -}; - -export const Default: StoryObj = { - render: () => , -}; diff --git a/components/TourModal/TourModal.styled.tsx b/components/TourModal/TourModal.styled.tsx deleted file mode 100644 index 8c2df666..00000000 --- a/components/TourModal/TourModal.styled.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import styled from 'styled-components'; -import Image from 'next/image'; - -import Column from '../Column/Column'; -import Row from '../Row/Row'; - -export const CardContent = styled(Column)` - align-items: center; - padding: 32px 24px; -`; - -export const CardFooter = styled(Row)` - justify-content: center; - gap: 16px; - padding: 16px 0; - border-top: 1px solid #e6e8f0; -`; - -export const Content = styled(Column)` - width: 100%; -`; - -export const K1Ray = styled(Image)` - height: 162px; - width: 192px; - margin: 24px 0; -`; diff --git a/components/TourModal/TourModal.tsx b/components/TourModal/TourModal.tsx deleted file mode 100644 index 397534d9..00000000 --- a/components/TourModal/TourModal.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { FunctionComponent } from 'react'; - -import Modal, { IModalProps } from '../Modal/Modal'; -import Typography from '../Typography/Typography'; -import Button from '../Button/Button'; - -import { CardContent, CardFooter, Content, K1Ray } from './TourModal.styled'; - -import kubefirstRay from '@/assets/ray.svg'; -import { BISCAY, VOLCANIC_SAND } from '@/constants/colors'; - -interface TourModalProps extends Omit { - onSkip: () => void; - onTakeTour: () => void; -} - -const TourCard: FunctionComponent = ({ onSkip, onTakeTour, ...modalProps }) => { - return ( - - - - - Welcome to kubefirst! - - - - We’d like to show you a couple of the features we’ve included to get you started. - - - - - - - - - ); -}; - -export default TourCard; diff --git a/components/UninstallApplication/UninstallApplication.stories.tsx b/components/UninstallApplication/UninstallApplication.stories.tsx deleted file mode 100644 index 535e309a..00000000 --- a/components/UninstallApplication/UninstallApplication.stories.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; - -import UninstallApplication from './UninstallApplication'; - -const meta: Meta = { - title: 'Components/UninstallApplication', - component: UninstallApplication, -}; - -export default meta; - -type Story = StoryObj; - -export const Default: Story = { - args: { - application: 'flappy', - cluster: 'development', - isOpen: true, - }, -}; diff --git a/components/UninstallApplication/UninstallApplication.styled.ts b/components/UninstallApplication/UninstallApplication.styled.ts deleted file mode 100644 index 4587b2a3..00000000 --- a/components/UninstallApplication/UninstallApplication.styled.ts +++ /dev/null @@ -1,35 +0,0 @@ -'use client'; -import styled from 'styled-components'; - -import Column from '../Column/Column'; -import NextLinkComp from '../NextLink/NextLink'; -import Row from '../Row/Row'; - -export const Content = styled(Column)` - gap: 8px; - margin-left: 38px; -`; - -export const CopyTextContainer = styled(Row)` - flex-wrap: wrap; - align-items: baseline; - gap: 3px; - margin-bottom: 24px; -`; - -export const Footer = styled.div` - display: flex; - gap: 8px; - justify-content: flex-end; - margin-top: 34px; -`; - -export const Header = styled.div` - display: flex; - gap: 12px; - margin-bottom: 8px; -`; - -export const NextLink = styled(NextLinkComp)` - display: inline; -`; diff --git a/components/UninstallApplication/UninstallApplication.tsx b/components/UninstallApplication/UninstallApplication.tsx deleted file mode 100644 index f33bda1c..00000000 --- a/components/UninstallApplication/UninstallApplication.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { FunctionComponent, PropsWithChildren } from 'react'; -import Box from '@mui/material/Box'; -import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; - -import Typography from '../Typography/Typography'; -import Modal from '../Modal/Modal'; -import Button from '../Button/Button'; - -import { Content, Footer, Header } from './UninstallApplication.styled'; - -import { LAUGHING_ORANGE, VOLCANIC_SAND } from '@/constants/colors'; - -export interface UninstallApplicationProps extends PropsWithChildren { - application: string; - canDeleteSelectedApp: boolean; - cluster: string; - isOpen: boolean; - isLoading: boolean; - onDelete: () => void; - onCloseModal?: () => void; -} - -const UninstallApplication: FunctionComponent = ({ - application, - canDeleteSelectedApp, - cluster, - isOpen, - onDelete, - onCloseModal, -}) => { - return ( - - -
- - - {canDeleteSelectedApp ? 'Uninstall application' : 'Application not found'} - -
- - <> - {canDeleteSelectedApp ? ( - - Are you sure you want to uninstall {application} from cluster{' '} - {cluster}? - - ) : ( - <> - - It appears that this application has been manually removed from your repository. - - Do you want to remove the application tile from this view? - - )} - - -
- - -
-
-
- ); -}; - -export default UninstallApplication; diff --git a/components/UpgradeModal/UpgradeModal.stories.tsx b/components/UpgradeModal/UpgradeModal.stories.tsx deleted file mode 100644 index e186353e..00000000 --- a/components/UpgradeModal/UpgradeModal.stories.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { Meta, StoryObj } from '@storybook/react'; - -import Button from '../Button/Button'; - -import UpgradeModalComponent from './UpgradeModal'; - -import useModal from '@/hooks/useModal'; - -const meta: Meta = { - component: UpgradeModalComponent, -}; - -export default meta; - -const UpgradeModal = () => { - const { isOpen, openModal, closeModal } = useModal(true); - return ( -
- - -
- ); -}; - -export const Default: StoryObj = { - render: () => , -}; diff --git a/components/UpgradeModal/UpgradeModal.styled.ts b/components/UpgradeModal/UpgradeModal.styled.ts deleted file mode 100644 index efff4230..00000000 --- a/components/UpgradeModal/UpgradeModal.styled.ts +++ /dev/null @@ -1,41 +0,0 @@ -'use client'; -import styled from 'styled-components'; -import CloseIcon from '@mui/icons-material/Close'; - -import Column from '../Column/Column'; -import Row from '../Row/Row'; - -import { MIDNIGHT_EXPRESS } from '@/constants/colors'; - -export const Container = styled(Column)` - background-color: ${MIDNIGHT_EXPRESS}; - border-radius: 8px; - box-shadow: 0px 2px 4px rgba(100, 116, 139, 0.1); - padding: 16px 24px; - height: 408px; - width: 400px; -`; - -export const Content = styled(Column)` - align-items: center; - margin: 24px; -`; - -export const Close = styled(CloseIcon)` - cursor: pointer; - position: fixed; - top: 25px; - right: 25px; -`; - -export const Footer = styled(Row)` - gap: 16px; - justify-content: center; - padding: 16px 24px; -`; - -export const Header = styled(Row)` - gap: 16px; - padding-top: 24px; - text-transform: capitalize; -`; diff --git a/components/UpgradeModal/UpgradeModal.tsx b/components/UpgradeModal/UpgradeModal.tsx deleted file mode 100644 index 41e0338a..00000000 --- a/components/UpgradeModal/UpgradeModal.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { FunctionComponent } from 'react'; -import Image from 'next/image'; -import StarBorderOutlinedIcon from '@mui/icons-material/StarBorderOutlined'; - -import Modal from '../Modal/Modal'; -import Button from '../Button/Button'; -import Typography from '../Typography/Typography'; - -import { Container, Content, Close, Footer, Header } from './UpgradeModal.styled'; - -import { ASMANI_SKY, ECHO_BLUE, MIDNIGHT_EXPRESS, WHITE } from '@/constants/colors'; -import RocketIcon from '@/assets/rocket.svg'; - -export interface UpgradeModalProps { - isOpen: boolean; - clusterLimitText: string; - clusterLimitDescription: string; - ctaText: string; - closeModal: () => void; -} - -const UpgradeModal: FunctionComponent = ({ - ctaText, - closeModal, - clusterLimitText, - clusterLimitDescription, - isOpen, -}) => { - const handleRedirect = () => { - return window.open(`${location.origin}/settings/subscription/plans`, '_blank'); - }; - - return ( - - -
- -
- - rocket - - {clusterLimitText} - - - {clusterLimitDescription} - - -
- -
-
-
- ); -}; - -export default UpgradeModal; diff --git a/components/controlledFields/ControlledAutoComplete/ControlledTagsAutoComplete.tsx b/components/controlledFields/ControlledAutoComplete/ControlledTagsAutoComplete.tsx deleted file mode 100644 index 6a470523..00000000 --- a/components/controlledFields/ControlledAutoComplete/ControlledTagsAutoComplete.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { Control, Controller, FieldValues, UseControllerProps } from 'react-hook-form'; - -import { AutocompleteTags } from '../../Autocomplete/Autocomplete'; - -import { ClusterEnvironment } from '@/types/provision'; - -export interface ControlledTagsAutoCompleteProps - extends UseControllerProps { - label: string; - required?: boolean; - control: Control; - onChange: (value?: ClusterEnvironment) => void; - onTagDelete: () => void; - onAddNewEnvironment?: () => void; - options: ClusterEnvironment[]; - createEnvironment?: boolean; -} - -function ControlledTagsAutocomplete({ - label, - onChange, - onTagDelete, - options, - createEnvironment, - onAddNewEnvironment, - ...rest -}: ControlledTagsAutoCompleteProps) { - return ( - ( - - )} - /> - ); -} - -export default ControlledTagsAutocomplete; diff --git a/containers/Application/Application.tsx b/containers/Application/Application.tsx deleted file mode 100644 index f2c43920..00000000 --- a/containers/Application/Application.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React, { FunctionComponent, useCallback, useEffect, useState } from 'react'; - -import ApplicationComponent, { - ApplicationProps as AppCompProps, -} from '@/components/Application/Application'; -import { useAppDispatch, useAppSelector } from '@/redux/store'; -import { checkSiteReadiness } from '@/redux/thunks/readiness.thunk'; - -export interface ApplicationProps extends Omit { - links?: Array; - onUninstall: () => void; -} - -const isGitLink = (url: string) => url.includes('github') || url.includes('gitlab'); - -const Application: FunctionComponent = ({ - links: applicationLinks, - onUninstall, - ...props -}) => { - const [firstLoad, setFirstLoad] = useState(false); - - const [links, setLinks] = useState<{ [url: string]: boolean } | undefined>( - applicationLinks?.reduce((previous, current) => { - return { ...previous, [current]: isGitLink(current) }; - }, {}), - ); - - const dispatch = useAppDispatch(); - const availableSites = useAppSelector(({ readiness }) => readiness.availableSites); - - const isSiteAvailable = useCallback( - (url: string) => { - return availableSites.includes(url) || isGitLink(url); - }, - [availableSites], - ); - - const checkSiteAvailability = useCallback( - (url: string) => { - const isAvailable = isSiteAvailable(url); - - if (!isAvailable) { - return dispatch(checkSiteReadiness({ url })); - } - }, - [dispatch, isSiteAvailable], - ); - - useEffect(() => { - if (availableSites.length) { - setLinks( - links && - Object.keys(links).reduce( - (previous, current) => ({ ...previous, [current]: isSiteAvailable(current) }), - {}, - ), - ); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [availableSites]); - - useEffect(() => { - const interval = setInterval( - () => - links && - Object.keys(links).map((url) => { - const isAvailable = links[url]; - !isAvailable && checkSiteAvailability(url); - }), - 20000, - ); - return () => clearInterval(interval); - }); - - useEffect(() => { - if (!firstLoad) { - setFirstLoad(true); - links && - Object.keys(links).map(async (url) => { - await checkSiteAvailability(url); - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ; -}; - -export default Application; diff --git a/containers/Applications/Applications.styled.ts b/containers/Applications/Applications.styled.ts deleted file mode 100644 index 541fd9d2..00000000 --- a/containers/Applications/Applications.styled.ts +++ /dev/null @@ -1,46 +0,0 @@ -'use client'; -import Link from 'next/link'; -import styled from 'styled-components'; - -import Row from '@/components/Row/Row'; -import ApplicationsFilterComp from '@/components/ApplicationsFilter/ApplicationsFilter'; -import { media } from '@/utils/media'; - -export const Container = styled.div` - height: calc(100vh - 64px); - margin: 0 auto; - overflow: auto; - padding: 40px 0 0 40px; - width: calc(100% - 40px); -`; - -export const Content = styled.div``; - -export const Header = styled.div` - color: ${({ theme }) => theme.colors.volcanicSand}; - display: flex; - flex-direction: column; - gap: 8px; - margin-bottom: 24px; -`; - -export const LearnMoreLink = styled(Link)` - color: ${({ theme }) => theme.colors.primary}; - text-decoration: none; -`; - -export const ApplicationsContainer = styled(Row)` - gap: 16px; - flex-wrap: wrap; - margin-bottom: 16px; -`; - -export const ApplicationsFilter = styled(ApplicationsFilterComp)` - width: fit-content; - min-width: 310px; - margin-bottom: 16px; - - ${media.greaterThan('sm')` - width: calc(100% - 70px); - `} -`; diff --git a/containers/Applications/Applications.tsx b/containers/Applications/Applications.tsx deleted file mode 100644 index 9b64ec0f..00000000 --- a/containers/Applications/Applications.tsx +++ /dev/null @@ -1,314 +0,0 @@ -'use client'; -import React, { FunctionComponent, useEffect, useCallback, useState, useMemo } from 'react'; -import Box from '@mui/material/Box'; -import Tabs from '@mui/material/Tabs'; -import sortBy from 'lodash/sortBy'; -import { useSession } from 'next-auth/react'; - -import Application from '../Application/Application'; -import GitOpsCatalog from '../GitOpsCatalog/GitOpsCatalog'; - -import { - Container, - Content, - Header, - LearnMoreLink, - ApplicationsContainer, - ApplicationsFilter, -} from './Applications.styled'; - -import TabPanel, { Tab, a11yProps } from '@/components/Tab/Tab'; -import Typography from '@/components/Typography/Typography'; -import { - getClusterApplications, - getGitOpsCatalogApps, - uninstallGitOpsApp, - validateGitOpsApplication, -} from '@/redux/thunks/applications.thunk'; -import { sendTelemetryEvent } from '@/redux/thunks/api.thunk'; -import { useAppDispatch, useAppSelector } from '@/redux/store'; -import { DOCS_LINK } from '@/constants'; -import { BISCAY, SALTBOX_BLUE, VOLCANIC_SAND } from '@/constants/colors'; -import { - addAppToQueue, - removeAppFromQueue, - setFilterState, - setSelectedClusterApplication, -} from '@/redux/slices/applications.slice'; -import { ClusterStatus, WORKLOAD_CLUSTER_TYPES } from '@/types/provision'; -import { ClusterApplication, GitOpsCatalogApp } from '@/types/applications'; -import useModal from '@/hooks/useModal'; -import UninstallApplication from '@/components/UninstallApplication/UninstallApplication'; - -enum APPLICATION_TAB { - PROVISIONED, - GITOPS_CATALOG, -} - -enum Target { - TEMPLATE = 'Template', - CLUSTER = 'Cluster', -} - -const TARGET_OPTIONS = Object.values(Target); - -const Applications: FunctionComponent = () => { - const [searchTerm, setSearchTerm] = useState(''); - const [activeTab, setActiveTab] = useState(0); - const { isOpen, openModal: openDeleteModal, closeModal: closeDeleteModal } = useModal(); - const { data: session } = useSession(); - - const { - clusterApplications, - isTelemetryDisabled, - clusterMap, - filter, - selectedCategories, - gitOpsCatalogApps, - installedClusterAppNames, - isLoading, - managementCluster, - selectedClusterApplication, - canDeleteSelectedApp, - appsQueue, - } = useAppSelector(({ config, applications, api }) => ({ - isTelemetryDisabled: config.isTelemetryDisabled, - clusterMap: api.clusterMap, - managementCluster: api.managementCluster, - ...applications, - })); - - const dispatch = useAppDispatch(); - - const handleLinkClick = useCallback( - (url: string, name: string) => { - if (!isTelemetryDisabled) { - const event = `console.${name.toLowerCase()}.link`.replace(/ /g, ''); - dispatch(sendTelemetryEvent({ event })); - } - }, - [dispatch, isTelemetryDisabled], - ); - - const handleChange = (_: React.SyntheticEvent, newValue: number) => { - setActiveTab(newValue); - }; - const catalogApps = useMemo( - () => - filter.target === Target.TEMPLATE - ? gitOpsCatalogApps.filter((app) => !app.secret_keys?.length) - : gitOpsCatalogApps, - [filter.target, gitOpsCatalogApps], - ); - - const clusterSelectOptions = useMemo((): { label: string; value: string }[] => { - if (!filter.target) { - return []; - } - - if (filter.target === Target.TEMPLATE) { - return WORKLOAD_CLUSTER_TYPES.map((type) => ({ label: type, value: type })); - } - - return Object.keys(clusterMap) - .filter((key) => { - const cluster = clusterMap[key]; - return cluster.status === ClusterStatus.PROVISIONED; - }) - .map((clusterName) => ({ - label: clusterName, - value: clusterName, - })); - }, [filter.target, clusterMap]); - - const filteredApps = useMemo(() => { - return clusterApplications.filter((app) => - app.name.toLowerCase().includes(searchTerm?.toLowerCase() as string), - ); - }, [clusterApplications, searchTerm]); - - const uninstalledCatalogApps = useMemo( - () => catalogApps.filter((app) => !clusterApplications.map((s) => s.name).includes(app.name)), - [clusterApplications, catalogApps], - ); - - const targetOptions = useMemo(() => { - const options = TARGET_OPTIONS.map((target) => ({ label: target, value: target })); - if (managementCluster?.cloudProvider === 'k3d') { - return options.filter(({ value }) => value !== Target.TEMPLATE); - } - - return options; - }, [managementCluster?.cloudProvider]); - - const filteredCatalogApps = useMemo(() => { - let apps: GitOpsCatalogApp[] = []; - if (!selectedCategories.length) { - apps = uninstalledCatalogApps; - } else { - apps = uninstalledCatalogApps.filter( - ({ category, name }) => - category && - selectedCategories.includes(category) && - !installedClusterAppNames.includes(name), - ); - } - - return sortBy(apps, (app) => app.display_name).filter((app) => - app.name.toLowerCase().includes(searchTerm?.toLowerCase() as string), - ); - }, [selectedCategories, uninstalledCatalogApps, installedClusterAppNames, searchTerm]); - - const handleOpenUninstallModalConfirmation = useCallback(() => { - openDeleteModal(); - }, [openDeleteModal]); - - const handleCloseUninstallModalConfirmation = useCallback(() => { - if (!isLoading) { - dispatch(removeAppFromQueue(selectedClusterApplication?.name as string)); - } - closeDeleteModal(); - }, [closeDeleteModal, dispatch, isLoading, selectedClusterApplication?.name]); - - const validateUninstallApplication = useCallback( - (application: ClusterApplication) => { - dispatch(addAppToQueue(application?.name as string)); - dispatch(setSelectedClusterApplication(application)); - - dispatch( - validateGitOpsApplication({ - application, - }), - ).then(() => { - dispatch(removeAppFromQueue(selectedClusterApplication?.name as string)); - handleOpenUninstallModalConfirmation(); - }); - }, - [dispatch, handleOpenUninstallModalConfirmation, selectedClusterApplication?.name], - ); - - const handleUninstallApplication = useCallback(() => { - dispatch(addAppToQueue(selectedClusterApplication?.name as string)); - closeDeleteModal(); - dispatch( - uninstallGitOpsApp({ - application: selectedClusterApplication as ClusterApplication, - user: (session?.user?.email as string) || 'kbot', - }), - ); - }, [closeDeleteModal, dispatch, selectedClusterApplication, session?.user?.email]); - - useEffect(() => { - if (managementCluster?.cloudProvider) { - dispatch(getGitOpsCatalogApps(managementCluster?.cloudProvider)); - } - }, [dispatch, managementCluster?.cloudProvider]); - - useEffect(() => { - const { cluster } = filter; - if (cluster) { - dispatch(getClusterApplications({ clusterName: cluster })); - } - }, [dispatch, filter]); - - const Apps = useMemo( - () => ( - <> - - {filteredApps.map((application: ClusterApplication) => ( - validateUninstallApplication(application)} - /> - ))} - - - ), - [appsQueue, filteredApps, handleLinkClick, validateUninstallApplication], - ); - - return ( - -
- Applications Overview -
- <> - - - Installed applications} - {...a11yProps(APPLICATION_TAB.PROVISIONED)} - sx={{ textTransform: 'initial', mr: 3 }} - /> - - GitOps catalog} - {...a11yProps(APPLICATION_TAB.GITOPS_CATALOG)} - sx={{ textTransform: 'initial' }} - /> - - - - {activeTab === APPLICATION_TAB.PROVISIONED ? ( - - Click on a link to access the service Kubefirst has provisioned for you.{' '} - handleLinkClick(DOCS_LINK, 'docs')} - > - Learn more - - - ) : ( - - Add the latest version of your favourite application to your cluster.{' '} - handleLinkClick(DOCS_LINK, 'docs')} - > - Learn more - - - )} - - {managementCluster?.clusterName && ( - ({ label: app.name, value: app.name }))} - onFilterChange={(filter) => dispatch(setFilterState(filter))} - onSearchChange={setSearchTerm} - defaultCluster={managementCluster?.clusterName as string} - /> - )} - - {Apps} - - - - - - - {isOpen && ( - - )} -
- ); -}; - -export default Applications; diff --git a/containers/Billing/Billing.styled.ts b/containers/Billing/Billing.styled.ts deleted file mode 100644 index 70d88767..00000000 --- a/containers/Billing/Billing.styled.ts +++ /dev/null @@ -1,23 +0,0 @@ -import styled from 'styled-components'; - -import { WHITE } from '@/constants/colors'; - -export const Container = styled.div` - width: 100%; -`; - -export const Header = styled.div` - background-color: ${WHITE}; - border-radius: 8px 8px 0px 0px; - display: flex; - justify-content: space-between; - padding: 16px 24px; - margin-top: 24px; -`; - -export const NoLicenseContainer = styled.div` - display: flex; - align-items: center; - flex-direction: column; - margin-top: 100px; -`; diff --git a/containers/Billing/Billing.tsx b/containers/Billing/Billing.tsx deleted file mode 100644 index a62d3e90..00000000 --- a/containers/Billing/Billing.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { FunctionComponent, useEffect, useMemo } from 'react'; -import Image from 'next/image'; - -import { Container, Header, NoLicenseContainer } from './Billing.styled'; - -import Typography from '@/components/Typography/Typography'; -import NextLink from '@/components/NextLink/NextLink'; -import Loading from '@/components/Loading/Loading'; -import { ClusterUsageTable } from '@/components/ClusterUsageTable/ClusterUsageTable'; -import { ASWAD_BLACK, BISCAY, VOLCANIC_SAND } from '@/constants/colors'; -import { useAppDispatch, useAppSelector } from '@/redux/store'; -import { getClusterUsage } from '@/redux/thunks/subscription.thunk'; -import MagnifyGlass from '@/assets/magnify-glass.svg'; -import { selectHasLicenseKey } from '@/redux/selectors/subscription.selector'; - -const Billing: FunctionComponent = () => { - const dispatch = useAppDispatch(); - - const { isLoading, license, clusterUsageList } = useAppSelector( - ({ subscription }) => subscription, - ); - - const hasLicenseKey = useAppSelector(selectHasLicenseKey()); - - const total = useMemo( - () => - clusterUsageList.length && - clusterUsageList.reduce( - (previousValue, currentValue) => previousValue + currentValue.total, - 0, - ), - [clusterUsageList], - ); - - useEffect(() => { - if (license?.licenseKey) { - dispatch(getClusterUsage(license?.licenseKey)); - } - }, [dispatch, license?.licenseKey]); - - return ( - - {hasLicenseKey ? ( - <> - - If at any time you need to update your billing card you can do so - by changing your card via Stripe. - -
- - Cluster usage for current month - - - ${total && total.toFixed(2)} - -
- {isLoading ? : } - - ) : ( - - magnify-glass - - You haven’t exceeded the Free Plan cluster usage yet - - - Once you do, you will be able to see your cluster usage and billing here. - - - )} -
- ); -}; - -export default Billing; diff --git a/containers/CancelSubscription/CancelSubscription.tsx b/containers/CancelSubscription/CancelSubscription.tsx deleted file mode 100644 index a9d5466e..00000000 --- a/containers/CancelSubscription/CancelSubscription.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { FunctionComponent, ReactNode, useCallback, useMemo } from 'react'; -import { FormProvider, useForm } from 'react-hook-form'; - -import CancelSubscriptionForm from './CancelSubscriptionForm/CancelSubscriptionForm'; -import CancelSubscriptionConfirmation from './CancelSubscriptionConfirmation/CancelSubscriptionConfirmation'; - -import useStep from '@/hooks/useStep'; -import { CancelSubscriptionFields, UserRequest } from '@/types/subscription'; -import Modal from '@/components/Modal/Modal'; -import { useAppDispatch } from '@/redux/store'; -import { createUserRequest, validateLicenseKey } from '@/redux/thunks/subscription.thunk'; - -export enum MODAL_STEP { - FORM = 0, - CONFIRMATION = 1, -} - -export interface CancelSubscriptionProps { - isOpen: boolean; - closeModal: () => void; -} - -const CancelSubscription: FunctionComponent = ({ isOpen, closeModal }) => { - const { currentStep, goToNext, goTo } = useStep(); - const dispatch = useAppDispatch(); - - const methods = useForm({ - mode: 'onChange', - }); - - const handleCancelSubscription = useCallback( - (userRequest: UserRequest) => { - dispatch(createUserRequest(userRequest)).then(() => { - goToNext(); - dispatch(validateLicenseKey()); - }); - }, - [dispatch, goToNext], - ); - - const handleClose = useCallback(() => { - closeModal(); - goTo(0); - }, [closeModal, goTo]); - - const modalComponents = useMemo( - () => - ({ - [MODAL_STEP.FORM]: ( - - - - ), - [MODAL_STEP.CONFIRMATION]: , - } as { [key: string]: ReactNode }), - [closeModal, handleCancelSubscription, handleClose, methods], - ); - - const modalComponent = useMemo( - () => modalComponents && modalComponents[currentStep as unknown as string], - [currentStep, modalComponents], - ); - - return ( - - <>{modalComponent} - - ); -}; - -export default CancelSubscription; diff --git a/containers/CancelSubscription/CancelSubscriptionConfirmation/CancelSubscriptionConfirmation.styled.ts b/containers/CancelSubscription/CancelSubscriptionConfirmation/CancelSubscriptionConfirmation.styled.ts deleted file mode 100644 index 8e8a08cd..00000000 --- a/containers/CancelSubscription/CancelSubscriptionConfirmation/CancelSubscriptionConfirmation.styled.ts +++ /dev/null @@ -1,16 +0,0 @@ -import styled from 'styled-components'; -import { Box } from '@mui/material'; - -export const Container = styled(Box)` - align-items: center; - display: flex; - flex-direction: column; - padding: 32px 24px; - width: 452px; -`; - -export const Footer = styled.div` - display: flex; - justify-content: center; - padding: 16px; -`; diff --git a/containers/CancelSubscription/CancelSubscriptionConfirmation/CancelSubscriptionConfirmation.tsx b/containers/CancelSubscription/CancelSubscriptionConfirmation/CancelSubscriptionConfirmation.tsx deleted file mode 100644 index cbb20d95..00000000 --- a/containers/CancelSubscription/CancelSubscriptionConfirmation/CancelSubscriptionConfirmation.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, { FunctionComponent } from 'react'; -import { Divider } from '@mui/material'; -import Image from 'next/image'; - -import { Container, Footer } from './CancelSubscriptionConfirmation.styled'; - -import Cancel from '@/assets/cancel.svg'; -import Typography from '@/components/Typography/Typography'; -import { BISCAY, VOLCANIC_SAND } from '@/constants/colors'; -import Button from '@/components/Button/Button'; - -export interface CancelSubscriptionConfirmationProps { - closeModal: () => void; -} - -const CancelSubscriptionConfirmation: FunctionComponent = ({ - closeModal, -}) => { - return ( - <> - - - We’re sorry to see you go! - - cancel-image - - You will receive an email within 24 hours confirming that your subscription has been - cancelled and your plan has been downgraded. - - - -
- -
- - ); -}; - -export default CancelSubscriptionConfirmation; diff --git a/containers/CancelSubscription/CancelSubscriptionForm/CancelSubscriptionForm.styled.ts b/containers/CancelSubscription/CancelSubscriptionForm/CancelSubscriptionForm.styled.ts deleted file mode 100644 index 4795c7d5..00000000 --- a/containers/CancelSubscription/CancelSubscriptionForm/CancelSubscriptionForm.styled.ts +++ /dev/null @@ -1,37 +0,0 @@ -import styled from 'styled-components'; -import { Box } from '@mui/material'; - -export const Container = styled(Box)` - width: 582px; -`; - -export const SubscriptionOptions = styled.div` - display: flex; - flex-direction: column; - gap: 16px; - padding: 0 24px; - margin-bottom: 24px; -`; - -export const Header = styled.div` - align-items: center; - display: flex; - justify-content: space-between; - padding: 24px; - - & > svg { - cursor: pointer; - } -`; - -export const Footer = styled.div` - display: flex; - justify-content: flex-end; - gap: 16px; - padding: 16px 24px; -`; - -export const Description = styled.div` - padding: 0 24px; - margin-bottom: 32px; -`; diff --git a/containers/CancelSubscription/CancelSubscriptionForm/CancelSubscriptionForm.tsx b/containers/CancelSubscription/CancelSubscriptionForm/CancelSubscriptionForm.tsx deleted file mode 100644 index 1de97fdc..00000000 --- a/containers/CancelSubscription/CancelSubscriptionForm/CancelSubscriptionForm.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import React, { FunctionComponent, useEffect, useState } from 'react'; -import { useFormContext } from 'react-hook-form'; -import { Divider } from '@mui/material'; -import CloseIcon from '@mui/icons-material/Close'; - -import { - Container, - Description, - Footer, - Header, - SubscriptionOptions, -} from './CancelSubscriptionForm.styled'; - -import Typography from '@/components/Typography/Typography'; -import { BISCAY, SALTBOX_BLUE, VOLCANIC_SAND } from '@/constants/colors'; -import ControlledCheckbox from '@/components/controlledFields/ControlledCheckbox/ControlledCheckbox'; -import Button from '@/components/Button/Button'; -import { CancelSubscriptionFields, UserRequest } from '@/types/subscription'; -import ControlledTextArea from '@/components/controlledFields/ControlledTextArea/ControlledTextArea'; -import { Required } from '@/components/TextField/TextField.styled'; - -export const CANCEL_SUBSCRIPTION_OPTIONS = [ - { - name: 'projectIsComplete', - label: 'The project I was working on is now complete', - }, - { - name: 'kubefirstIsTooExpensive', - label: 'kubefirst is too expensive', - }, - { - name: 'kubefirstIsDifficult', - label: 'kubefirst is too difficult to use', - }, - { - name: 'didnotUsePaidPlan', - label: 'I didn’t use the paid plan features', - }, - { - name: 'didnotProvideFunctionality', - label: 'kubefirst didn’t provide the functionality I needed', - }, - { - name: 'other', - label: 'Other', - }, -]; - -export interface CancelSubscriptionFormProps { - closeModal: () => void; - handleCancelSubscription: (userRequest: UserRequest) => void; -} - -const CancelSubscriptionForm: FunctionComponent = ({ - closeModal, - handleCancelSubscription, -}) => { - const [hasSelectedOption, setHasSelectedOption] = useState(false); - const { control, handleSubmit, watch, reset } = useFormContext(); - - const onSubmit = async ({ description, ...rest }: CancelSubscriptionFields) => { - const reasons = Object.keys(rest) - .filter((key) => rest && !!rest[key as never]) - .map((reason) => { - return CANCEL_SUBSCRIPTION_OPTIONS.find(({ name }) => name === reason)?.label; - }) - .join(', '); - - const requestData: UserRequest = { - requestData: JSON.stringify({ message: description, reason: reasons }), - type: 'cancel', - }; - - handleCancelSubscription(requestData); - reset(); - }; - - useEffect(() => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const subscription = watch(({ description, ...rest }) => { - setHasSelectedOption([...Object.values(rest)].includes(true)); - }); - - return () => subscription.unsubscribe(); - }, [watch]); - - return ( - -
- - Cancel my subscription - - -
- - - Before you go would you mind letting us know why? * - - - {CANCEL_SUBSCRIPTION_OPTIONS.map(({ name, label }) => ( - - ))} - - - - - -
- - -
-
- ); -}; - -export default CancelSubscriptionForm; diff --git a/containers/ClusterForms/ClusterCreationForm/AdvancedOptions/AdvancedOptions.styled.ts b/containers/ClusterForms/ClusterCreationForm/AdvancedOptions/AdvancedOptions.styled.ts deleted file mode 100644 index 0e904dc3..00000000 --- a/containers/ClusterForms/ClusterCreationForm/AdvancedOptions/AdvancedOptions.styled.ts +++ /dev/null @@ -1,22 +0,0 @@ -'use client'; -import styled from 'styled-components'; - -import Column from '@/components/Column/Column'; -import LearnMore from '@/components/LearnMore/LearnMore'; -import { EXCLUSIVE_PLUM } from '@/constants/colors'; - -export const InputContainer = styled(Column)` - .MuiFormGroup-root { - margin-left: 8px; - } - - & ${LearnMore} { - margin-left: 40px; - color: ${EXCLUSIVE_PLUM}; - - span, - a { - color: ${EXCLUSIVE_PLUM}; - } - } -`; diff --git a/containers/ClusterForms/ClusterCreationForm/AdvancedOptions/AdvancedOptions.tsx b/containers/ClusterForms/ClusterCreationForm/AdvancedOptions/AdvancedOptions.tsx deleted file mode 100644 index 2b28baf9..00000000 --- a/containers/ClusterForms/ClusterCreationForm/AdvancedOptions/AdvancedOptions.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import React, { FunctionComponent, useState } from 'react'; -import { useFormContext } from 'react-hook-form'; - -import { InputContainer } from './AdvancedOptions.styled'; - -import LearnMore from '@/components/LearnMore/LearnMore'; -import Typography from '@/components/Typography/Typography'; -import Checkbox from '@/components/controlledFields/ControlledCheckbox/ControlledCheckbox'; -import ControlledTextField from '@/components/controlledFields/ControlledTextField/ControlledTextField'; -import ControlledAutocomplete from '@/components/controlledFields/ControlledAutoComplete/ControlledAutoComplete'; -import ControlledRadioGroup from '@/components/controlledFields/ControlledRadioGroup/ControlledRadioGroup'; -import { useAppSelector } from '@/redux/store'; -import { ImageRepository, NewWorkloadClusterConfig } from '@/types/provision'; -import { EXCLUSIVE_PLUM } from '@/constants/colors'; - -const AdvancedOptions: FunctionComponent = () => { - const [isCloudFlareSelected, setIsCloudFlareSelected] = useState(false); - - const { installType } = useAppSelector(({ installation }) => installation); - - const { control, getValues } = useFormContext(); - - const { gitopsTemplateUrl, gitopsTemplateBranch, imageRepository } = getValues(); - - return ( - <> - - - - - - By default kubefirst uses ssh to create your cluster check the below to use https instead{' '} - - - - setIsCloudFlareSelected(value === 'cloudflare')} - rules={{ - required: false, - }} - /> - {isCloudFlareSelected && ( - - )} - - - Manage image repositories with - - - - - ); -}; - -export default AdvancedOptions; diff --git a/containers/ClusterForms/ClusterCreationForm/ClusterCreationForm.stories.tsx b/containers/ClusterForms/ClusterCreationForm/ClusterCreationForm.stories.tsx deleted file mode 100644 index be9881ac..00000000 --- a/containers/ClusterForms/ClusterCreationForm/ClusterCreationForm.stories.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import { Meta, StoryObj } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; -import { FormProvider, useForm } from 'react-hook-form'; - -import { NewWorkloadClusterConfig } from '../../../types/provision'; - -import ClusterCreationForm from './ClusterCreationForm'; - -const meta: Meta = { - title: 'Forms/ClusterCreationForm', - component: ClusterCreationForm, -}; - -export default meta; - -const ClusterCreationFormWithHooks = () => { - const methods = useForm(); - - return ( - -
action('onSubmit')}> - - - -
- ); -}; - -export const Default: StoryObj = { - render: () => , -}; diff --git a/containers/ClusterForms/ClusterCreationForm/ClusterCreationForm.styled.ts b/containers/ClusterForms/ClusterCreationForm/ClusterCreationForm.styled.ts deleted file mode 100644 index 5e693621..00000000 --- a/containers/ClusterForms/ClusterCreationForm/ClusterCreationForm.styled.ts +++ /dev/null @@ -1,45 +0,0 @@ -import Column from '@/components/Column/Column'; -import Button from '@/components/Button/Button'; -import styled, { css } from '@/app/lib/styled-components'; - -export const AdvancedOptionsButton = styled('button') - .withConfig({ - shouldForwardProp: (prop) => prop !== 'expanded', - }) - .attrs({ type: 'button' })<{ - expanded?: boolean; -}>` - border: none; - background-color: transparent; - display: flex; - cursor: pointer; - - svg { - transition: transform 0.4s ease; - } - - ${({ expanded }) => - expanded && - css` - svg { - transform: rotate(180deg); - } - `} -`; - -export const Container = styled(Column)` - gap: 32px; - width: 100%; -`; - -export const StyledButton = styled(Button)` - width: 163px; - margin-top: -8px; - display: flex; - gap: 8px; - - svg { - height: 20px; - width: 20px; - } -`; diff --git a/containers/ClusterForms/ClusterCreationForm/ClusterCreationForm.tsx b/containers/ClusterForms/ClusterCreationForm/ClusterCreationForm.tsx deleted file mode 100644 index 1f212714..00000000 --- a/containers/ClusterForms/ClusterCreationForm/ClusterCreationForm.tsx +++ /dev/null @@ -1,332 +0,0 @@ -import React, { ComponentProps, FC, useCallback, useEffect, useMemo } from 'react'; -import { useFormContext } from 'react-hook-form'; -import Box from '@mui/material/Box'; - -import { usePhysicalClustersPermissions } from '../../../hooks/usePhysicalClustersPermission'; - -import { Container } from './ClusterCreationForm.styled'; -import { InputContainer } from './AdvancedOptions/AdvancedOptions.styled'; - -import ControlledAutocomplete from '@/components/controlledFields/ControlledAutoComplete/ControlledAutoComplete'; -import ControlledTextField from '@/components/controlledFields/ControlledTextField/ControlledTextField'; -import { useAppDispatch, useAppSelector } from '@/redux/store'; -import Typography from '@/components/Typography/Typography'; -import { ClusterType, NewWorkloadClusterConfig, ClusterEnvironment } from '@/types/provision'; -import { BISCAY, EXCLUSIVE_PLUM } from '@/constants/colors'; -import ControlledNumberInput from '@/components/controlledFields/ControlledNumberInput/ControlledNumberInput'; -import ControlledRadioGroup from '@/components/controlledFields/ControlledRadioGroup/ControlledRadioGroup'; -import { - LOWER_KEBAB_CASE_REGEX, - MIN_NODE_COUNT, - RESERVED_DRAFT_CLUSTER_NAME, - WORKLOAD_CLUSTER_OPTIONS, -} from '@/constants'; -import { updateDraftCluster } from '@/redux/slices/api.slice'; -import Modal from '@/components/Modal/Modal'; -import useModal from '@/hooks/useModal'; -import { CreateEnvironmentMenu } from '@/components/CreateEnvironmentMenu/CreateEnvironmentMenu'; -import { createEnvironment, getAllEnvironments } from '@/redux/thunks/environments.thunk'; -import { noop } from '@/utils/noop'; -import { clearEnvironmentError } from '@/redux/slices/environments.slice'; -import { InstallationType } from '@/types/redux'; -import { - getCloudDomains, - getCloudRegions, - getInstanceSizes, - getRegionZones, -} from '@/redux/thunks/api.thunk'; -import ControlledTagsAutocomplete from '@/components/controlledFields/ControlledAutoComplete/ControlledTagsAutoComplete'; -import { getCloudProviderAuth } from '@/utils/getCloudProviderAuth'; -import { selectApiState } from '@/redux/selectors/api.selector'; -import { selectEnvironmentsState } from '@/redux/selectors/environments.selector'; - -const ClusterCreationForm: FC> = (props) => { - const { isOpen, openModal, closeModal } = useModal(false); - const { managementCluster, cloudRegions, cloudZones, clusterMap, instanceSizes } = useAppSelector( - selectApiState(), - ); - const { environments, error } = useAppSelector(selectEnvironmentsState()); - - const dispatch = useAppDispatch(); - - const { - control, - getValues, - setValue, - watch, - formState: { errors }, - } = useFormContext(); - - const { type, nodeCount, instanceSize, cloudRegion, clusterName } = getValues(); - - const handleAddEnvironment = useCallback( - (environment: ClusterEnvironment) => { - setValue('environment', environment); - dispatch(createEnvironment(environment)) - .unwrap() - .then(() => { - closeModal(); - }) - .catch(noop); - }, - [dispatch, setValue, closeModal], - ); - - const clearEnvError = useCallback(() => { - dispatch(clearEnvironmentError()); - }, [dispatch]); - - const handleModalClose = useCallback(() => { - clearEnvError(); - closeModal(); - }, [clearEnvError, closeModal]); - - const handleRegionOnSelect = useCallback( - (region: string) => { - // if using google hold off on grabbing instances - // since it requires the zone as well - if (managementCluster) { - const { key, value } = getCloudProviderAuth(managementCluster); - if (managementCluster?.cloudProvider === InstallationType.GOOGLE) { - dispatch( - getRegionZones({ - region, - values: { [`${key}_auth`]: value }, - }), - ); - } else { - dispatch( - getInstanceSizes({ - installType: managementCluster?.cloudProvider, - region, - values: { [`${key}_auth`]: value }, - }), - ); - dispatch( - getCloudDomains({ - installType: managementCluster?.cloudProvider, - region, - }), - ); - } - } - }, - [dispatch, managementCluster], - ); - - const handleZoneSelect = (zone: string) => { - const { cloudRegion } = getValues(); - if (managementCluster && cloudRegion) { - const { key, value } = getCloudProviderAuth(managementCluster); - dispatch( - getInstanceSizes({ - installType: managementCluster?.cloudProvider, - region: cloudRegion, - zone, - values: { [`${key}_auth`]: value }, - }), - ); - } - }; - - const handleEnvChange = useCallback( - (env?: ClusterEnvironment) => { - setValue('environment', env); - }, - [setValue], - ); - - const handleTagDelete = useCallback(() => { - setValue('environment', undefined); - }, [setValue]); - - const { hasPermissions } = usePhysicalClustersPermissions(managementCluster?.cloudProvider); - const draftCluster = clusterMap[RESERVED_DRAFT_CLUSTER_NAME]; - const isVCluster = type === ClusterType.WORKLOAD_V_CLUSTER; - - const clusterOptions = useMemo(() => { - let clusterTypes; - - if (hasPermissions) { - clusterTypes = WORKLOAD_CLUSTER_OPTIONS; - } else { - clusterTypes = WORKLOAD_CLUSTER_OPTIONS.filter( - (option) => option.value !== ClusterType.WORKLOAD, - ); - } - - return clusterTypes; - }, [hasPermissions]); - - useEffect(() => { - const subscription = watch((values) => { - if (draftCluster) { - dispatch(updateDraftCluster({ ...draftCluster, ...values })); - } - }); - - return () => subscription.unsubscribe(); - }, [watch, dispatch, draftCluster]); - - useEffect(() => { - if (managementCluster) { - dispatch( - getCloudRegions({ - values: managementCluster, - installType: managementCluster.cloudProvider, - }), - ); - dispatch(getAllEnvironments()); - } - // i do not want to get cloud regions every time management cluster updates - // initial load and hard refresh is sufficient. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dispatch]); - - useEffect(() => { - if (cloudRegion && managementCluster) { - const { key, value } = getCloudProviderAuth(managementCluster); - dispatch( - getInstanceSizes({ - installType: managementCluster?.cloudProvider, - region: cloudRegion, - values: { [`${key}_auth`]: value }, - }), - ); - } - // i do not want to get instance sizes every time management cluster updates - // initial load/hard refresh/cloudRegion update is sufficient. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [cloudRegion]); - - return ( - - - Create workload cluster - - - - - Cluster type - - setValue('type', clusterType)} - /> - - - - - - - - - - (typeof value === 'string' && !clusterMap[value]) || - 'Please use a unique name that has not been previously provisioned', - reservedClusterName: (value) => - (typeof value === 'string' && value !== RESERVED_DRAFT_CLUSTER_NAME) || - 'Please use a cluster name that is not reserved', - }, - }} - onErrorText={errors.clusterName?.message} - /> - - {!isVCluster && ( - <> - ({ label: region, value: region }))} - onChange={handleRegionOnSelect} - /> - - {managementCluster?.cloudProvider === InstallationType.GOOGLE && ( - ({ - label: zone, - value: zone, - }))} - onChange={handleZoneSelect} - /> - )} - - ({ - label: instanceSize, - value: instanceSize, - }))} - defaultValue={instanceSize} - /> - - - - - - )} - - ); -}; - -export default ClusterCreationForm; diff --git a/containers/ClusterForms/shared/AuthForm/AuthForm.tsx b/containers/ClusterForms/shared/AuthForm/AuthForm.tsx index 278b4b59..4a6a34d0 100644 --- a/containers/ClusterForms/shared/AuthForm/AuthForm.tsx +++ b/containers/ClusterForms/shared/AuthForm/AuthForm.tsx @@ -330,7 +330,7 @@ const AuthForm: FunctionComponent = () => { /> )} - {apiKeyInfo?.fieldKeys.map(({ label, name, helperText }) => + {apiKeyInfo?.fieldKeys.map(({ label, name, helperText, defaultValue }) => isDigitalOcean && name === 'token' ? ( { // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore name={`${apiKeyInfo.authKey}.${name}`} + defaultValue={defaultValue} label={label} helperText={helperText} required diff --git a/containers/ClusterForms/shared/ClusterRunning.tsx b/containers/ClusterForms/shared/ClusterRunning.tsx index e397c877..31ad5d17 100644 --- a/containers/ClusterForms/shared/ClusterRunning.tsx +++ b/containers/ClusterForms/shared/ClusterRunning.tsx @@ -1,25 +1,25 @@ -import React, { FunctionComponent, useCallback } from 'react'; +import React, { FunctionComponent, useEffect } from 'react'; +import { noop } from 'lodash'; import ClusterReady from '@/components/ClusterReady/ClusterReady'; import { useAppDispatch, useAppSelector } from '@/redux/store'; -import { setSelectedCluster } from '@/redux/slices/applications.slice'; +import { getClusters } from '@/redux/thunks/api.thunk'; const ClusterRunning: FunctionComponent = () => { - const dispatch = useAppDispatch(); - const { managementCluster } = useAppSelector(({ api }) => api); + const dispatch = useAppDispatch(); const { clusterName, domainName, subDomainName, vaultAuth } = managementCluster ?? {}; const fullDomainName = `${subDomainName ? `${subDomainName}.${domainName}` : domainName}`; - const onOpenConsole = useCallback(() => { - dispatch(setSelectedCluster(managementCluster)); - }, [dispatch, managementCluster]); + useEffect(() => { + dispatch(getClusters()); + }, [dispatch]); return ( { installationStep, installType, values, - clusterMap, showCloudflareCaIssuerField, } = useAppSelector(({ api, installation, featureFlags }) => ({ ...api, @@ -176,7 +175,7 @@ const SetupForm: FunctionComponent = () => { useEffect(() => { checkAuth(); if (installType && values) { - dispatch(getCloudRegions({ installType, values })); + dispatch(getCloudRegions({ installType, values, validate: false })); } }, [checkAuth, dispatch, installType, values, trigger]); @@ -318,7 +317,7 @@ const SetupForm: FunctionComponent = () => { }, validate: { previouslyUsedClusterNames: (value) => - (typeof value === 'string' && !clusterMap[value]) || + typeof value === 'string' || 'Please use a unique name that has not been previously provisioned', civoClusterName: (value) => { if (installType === InstallationType.CIVO) { diff --git a/containers/ClusterManagement/ClusterManagement.styled.ts b/containers/ClusterManagement/ClusterManagement.styled.ts deleted file mode 100644 index 248b260a..00000000 --- a/containers/ClusterManagement/ClusterManagement.styled.ts +++ /dev/null @@ -1,56 +0,0 @@ -'use client'; -import Link from 'next/link'; -import styled from 'styled-components'; -import { styled as muiStyled } from '@mui/material/styles'; - -import Typography from '@/components/Typography/Typography'; -import Column from '@/components/Column/Column'; -import Row from '@/components/Row/Row'; -import Drawer from '@/components/Drawer/Drawer'; -import { VOLCANIC_SAND } from '@/constants/colors'; - -export const Content = styled(Column)` - flex: 1; - width: 100%; -`; - -export const Description = styled(Typography)` - color: ${VOLCANIC_SAND}; - margin-top: 8px; -`; - -export const Header = styled(Row)` - color: ${({ theme }) => theme.colors.volcanicSand}; - height: 70px; - align-items: center; - justify-content: space-between; - background-color: white; - border-top: 1px solid #e2e8f0; -`; - -export const LearnMoreLink = styled(Link)` - color: ${({ theme }) => theme.colors.primary}; - text-decoration: none; -`; - -export const LeftContainer = styled(Row)` - align-items: center; - margin-left: 24px; - gap: 42px; -`; - -export const StyledDrawer = muiStyled(Drawer, { - shouldForwardProp: (prop) => prop !== 'isBannerOpen', -})(({ isBannerOpen }: { isBannerOpen: boolean }) => ({ - '&.MuiDrawer-root': { - '.MuiBackdrop-root': { - backgroundColor: 'transparent', - }, - }, - '.MuiDrawer-paper': { - top: isBannerOpen ? '72px' : '65px', - boxShadow: '0px 2px 4px rgba(100, 116, 139, 0.16)', - width: '684px', - height: 'calc(100% - 65px)', - }, -})); diff --git a/containers/ClusterManagement/ClusterManagement.tsx b/containers/ClusterManagement/ClusterManagement.tsx deleted file mode 100644 index 5e583e3a..00000000 --- a/containers/ClusterManagement/ClusterManagement.tsx +++ /dev/null @@ -1,353 +0,0 @@ -'use client'; -import React, { FunctionComponent, useCallback, useEffect, useMemo } from 'react'; -import Box from '@mui/material/Box'; -import Tabs from '@mui/material/Tabs'; - -import { CreateClusterFlow } from './CreateClusterFlow/CreateClusterFlow'; -import { Content, Header, LeftContainer, StyledDrawer } from './ClusterManagement.styled'; - -import Button from '@/components/Button/Button'; -import Typography from '@/components/Typography/Typography'; -import { useAppDispatch, useAppSelector } from '@/redux/store'; -import { createWorkloadCluster, deleteCluster, downloadKubeconfig } from '@/redux/thunks/api.thunk'; -import { - ClusterCreationStep, - ClusterStatus, - ClusterType, - WorkloadCluster, -} from '@/types/provision'; -import useToggle from '@/hooks/useToggle'; -import useModal from '@/hooks/useModal'; -import DeleteCluster from '@/components/DeleteCluster/DeleteCluster'; -import TabPanel, { Tab, a11yProps } from '@/components/Tab/Tab'; -import { BISCAY, SALTBOX_BLUE } from '@/constants/colors'; -import { Flow } from '@/components/Flow/Flow'; -import ClusterTable from '@/components/ClusterTable/ClusterTable'; -import { - createDraftCluster, - removeDraftCluster, - setClusterCreationStep, -} from '@/redux/slices/api.slice'; -import { setPresentedClusterName } from '@/redux/slices/api.slice'; -import { InstallationType } from '@/types/redux'; -import { setClusterManagamentTab } from '@/redux/slices/config.slice'; -import { ClusterManagementTab, FeatureFlag } from '@/types/config'; -import { - DEFAULT_CLOUD_INSTANCE_SIZES, - KUBECONFIG_CLI_DETAILS, - RESERVED_DRAFT_CLUSTER_NAME, - SUGGESTED_WORKLOAD_NODE_COUNT, -} from '@/constants'; -import { getClusterTourStatus } from '@/redux/thunks/settings.thunk'; -import usePaywall from '@/hooks/usePaywall'; -import UpgradeModal from '@/components/UpgradeModal/UpgradeModal'; -import { selectUpgradeLicenseDefinition } from '@/redux/selectors/subscription.selector'; -import KubeConfigModal from '@/components/KubeConfigModal/KubeConfigModal'; -import { createNotification } from '@/redux/slices/notifications.slice'; -import useFeatureFlag from '@/hooks/useFeatureFlag'; -import Column from '@/components/Column/Column'; -import { SaasFeatures } from '@/types/subscription'; - -const ClusterManagement: FunctionComponent = () => { - const { - clusterCreationStep, - clusterManagementTab, - clusterMap, - isBannerOpen, - managementCluster, - presentedClusterName, - loading, - } = useAppSelector(({ api, queue, config, featureFlags, settings }) => ({ - clusterQueue: queue.clusterQueue, - clusterManagementTab: config.clusterManagementTab, - ...api, - ...featureFlags.flags, - ...settings, - })); - - const dispatch = useAppDispatch(); - const upgradeLicenseDefinition = useAppSelector(selectUpgradeLicenseDefinition()); - const { isEnabled: isSassSubscriptionEnabled } = useFeatureFlag(FeatureFlag.SAAS_SUBSCRIPTION); - - const { - isOpen: isUpgradeModalOpen, - openModal: openUpgradeModal, - closeModal: closeUpgradeModal, - } = useModal(); - const { canUseFeature } = usePaywall(); - const { instanceSize } = - DEFAULT_CLOUD_INSTANCE_SIZES[managementCluster?.cloudProvider ?? InstallationType.LOCAL]; - - const presentedCluster = useMemo( - () => clusterMap[presentedClusterName ?? ''], - [clusterMap, presentedClusterName], - ); - - const { - isOpen: createClusterFlowOpen, - open: openCreateClusterFlow, - close: closeCreateClusterFlow, - } = useToggle(); - - const { - isOpen: isDeleteModalOpen, - openModal: openDeleteModal, - closeModal: closeDeleteModal, - } = useModal(); - - const { - isOpen: isKubeconfigModalOpen, - openModal: openKubeconfigModal, - closeModal: closeKubeconfigModal, - } = useModal(); - - const handleMenuClose = useCallback(() => { - if (clusterCreationStep === ClusterCreationStep.CONFIG) { - dispatch(removeDraftCluster()); - } else { - dispatch(setClusterCreationStep(ClusterCreationStep.CONFIG)); - } - dispatch(setPresentedClusterName(undefined)); - closeCreateClusterFlow(); - }, [clusterCreationStep, dispatch, closeCreateClusterFlow]); - - const handleDeleteCluster = useCallback(() => { - if (presentedClusterName) { - dispatch(deleteCluster(presentedClusterName)) - .unwrap() - .then(() => { - closeDeleteModal(); - handleMenuClose(); - }); - } - }, [dispatch, presentedClusterName, closeDeleteModal, handleMenuClose]); - - const handleChange = useCallback( - (_: React.SyntheticEvent, tabIndex: number) => { - dispatch(setClusterManagamentTab(tabIndex)); - if (presentedClusterName) { - dispatch(setPresentedClusterName(undefined)); - } - }, - [dispatch, presentedClusterName], - ); - - const handleClusterSelect = useCallback( - (clusterName: string) => { - dispatch(setPresentedClusterName(clusterName)); - dispatch(setClusterCreationStep(ClusterCreationStep.DETAILS)); - openCreateClusterFlow(); - }, - [dispatch, openCreateClusterFlow], - ); - - const handleAddWorkloadCluster = useCallback(() => { - if (clusterCreationStep === ClusterCreationStep.CONFIG && managementCluster) { - const { - gitProvider, - cloudProvider, - domainName, - adminEmail, - gitAuth, - dnsProvider, - cloudRegion, - } = managementCluster; - - const draftCluster: WorkloadCluster = { - clusterId: RESERVED_DRAFT_CLUSTER_NAME, - clusterName: RESERVED_DRAFT_CLUSTER_NAME, - status: ClusterStatus.PROVISIONING, - type: ClusterType.WORKLOAD, - nodeCount: SUGGESTED_WORKLOAD_NODE_COUNT, - cloudProvider, - cloudRegion, - gitProvider, - domainName, - gitAuth, - adminEmail, - dnsProvider, - }; - - dispatch(createDraftCluster(draftCluster)); - } - openCreateClusterFlow(); - }, [clusterCreationStep, managementCluster, dispatch, openCreateClusterFlow]); - - const handleCreateCluster = () => { - if (clusterCreationStep !== ClusterCreationStep.DETAILS) { - const canCreateWorkloadClusters = canUseFeature(SaasFeatures.WorkloadClustersLimit); - - if (isSassSubscriptionEnabled && !canCreateWorkloadClusters) { - return openUpgradeModal(); - } - } - - if (clusterCreationStep !== ClusterCreationStep.DETAILS) { - dispatch(createWorkloadCluster()); - } - }; - - const handleDeleteMenuClick = useCallback( - (clusterName: string) => { - dispatch(setPresentedClusterName(clusterName)); - openDeleteModal(); - }, - [dispatch, openDeleteModal], - ); - - const handleDownloadKubeconfig = useCallback(async () => { - if ([InstallationType.AWS, InstallationType.GOOGLE].includes(presentedCluster.cloudProvider)) { - openKubeconfigModal(); - } else if (presentedCluster) { - const { clusterName } = presentedCluster; - dispatch(downloadKubeconfig({ presentedCluster })) - .unwrap() - .then((encodedString) => { - const downloadLink = document.createElement('a'); - downloadLink.href = encodedString; - downloadLink.download = `${clusterName}-kubeconfig`; - - document.body.appendChild(downloadLink); - - downloadLink.click(); - - document.body.removeChild(downloadLink); - }) - .catch((error) => { - dispatch( - createNotification({ - message: `Unable to download kubeconfig: ${error.message}`, - type: 'error', - snackBarOrigin: { vertical: 'top', horizontal: 'center' }, - }), - ); - }); - } - }, [dispatch, presentedCluster, openKubeconfigModal]); - - const { command, commandDocLink } = - KUBECONFIG_CLI_DETAILS[managementCluster?.cloudProvider ?? InstallationType.AWS]; - - useEffect(() => { - if (managementCluster) { - dispatch(getClusterTourStatus(managementCluster.clusterName)); - } - }, [dispatch, managementCluster]); - - return ( - <> -
- - Clusters - - - Graph view} - {...a11yProps(ClusterManagementTab.GRAPH_VIEW)} - sx={{ textTransform: 'initial', marginRight: '24px' }} - /> - List view} - {...a11yProps(ClusterManagementTab.LIST_VIEW)} - sx={{ textTransform: 'initial', marginRight: 0 }} - /> - - - - - -
- - - - {managementCluster && ( - - )} - - - - - - - - - - {!!presentedCluster && ( - - )} - {isUpgradeModalOpen && ( - - )} - {isKubeconfigModalOpen && ( - - )} - - ); -}; - -export default ClusterManagement; diff --git a/containers/ClusterManagement/CreateClusterFlow/CreateClusterFlow.styled.ts b/containers/ClusterManagement/CreateClusterFlow/CreateClusterFlow.styled.ts deleted file mode 100644 index 0e6b3247..00000000 --- a/containers/ClusterManagement/CreateClusterFlow/CreateClusterFlow.styled.ts +++ /dev/null @@ -1,58 +0,0 @@ -'use client'; -import styled from 'styled-components'; -import { styled as muiStyled, Box } from '@mui/material'; - -import Row from '@/components/Row/Row'; -import Column from '@/components/Column/Column'; -import HeadsUpNotification from '@/components/HeadsUpNotification/HeadsUpNotification'; -import { CHEFS_HAT } from '@/constants/colors'; - -export const CloseButton = styled.button` - display: flex; - align-items: center; - border: none; - background-color: transparent; - cursor: pointer; -`; - -export const Form = styled.form` - display: flex; - flex-direction: column; - height: 100%; -`; - -export const FormContent = styled(Column)` - flex: 1; - padding: 0 24px; - overflow-y: auto; - - ${HeadsUpNotification} { - margin-top: 20px; - } -`; - -export const Menu = muiStyled(Box)( - () => ` - min-width: 210px; - position: absolute; - bottom: -90px; - left: -148px; - width: 160px; - background-color: white; - border: 1px solid ${CHEFS_HAT}; - border-radius: 8px; - box-shadow: 0px 2px 4px 0px rgba(100, 116, 139, 0.25); - z-index: 1; -`, -); - -export const MenuHeader = styled(Row)` - flex-shrink: 0; - color: ${({ theme }) => theme.colors.volcanicSand}; - height: 70px; - justify-content: space-between; - align-items: center; - background-color: white; - padding: 0 24px; - border-bottom: 1px solid #e2e8f0; -`; diff --git a/containers/ClusterManagement/CreateClusterFlow/CreateClusterFlow.tsx b/containers/ClusterManagement/CreateClusterFlow/CreateClusterFlow.tsx deleted file mode 100644 index 886d76f5..00000000 --- a/containers/ClusterManagement/CreateClusterFlow/CreateClusterFlow.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import React, { FC, useCallback } from 'react'; -import CloseIcon from '@mui/icons-material/Close'; -import MoreHoriz from '@mui/icons-material/MoreHoriz'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemButton from '@mui/material/ListItemButton'; -import { ClickAwayListener } from '@mui/material'; -import { FormProvider, useForm } from 'react-hook-form'; - -import { CloseButton, Form, FormContent, Menu, MenuHeader } from './CreateClusterFlow.styled'; - -import Button from '@/components/Button/Button'; -import { FIRE_BRICK, SALTBOX_BLUE } from '@/constants/colors'; -import ClusterCreationForm from '@/containers/ClusterForms/ClusterCreationForm/ClusterCreationForm'; -import ClusterDetails from '@/components/ClusterDetails/ClusterDetails'; -import { - Cluster, - ClusterCreationStep, - ClusterStatus, - DraftCluster, - ManagementCluster, - NewWorkloadClusterConfig, -} from '@/types/provision'; -import { RESERVED_DRAFT_CLUSTER_NAME } from '@/constants'; -import useToggle from '@/hooks/useToggle'; -import Row from '@/components/Row/Row'; -import Typography from '@/components/Typography/Typography'; - -interface CreateClusterFlowProps { - cluster?: Cluster | DraftCluster; - clusterCreationStep: ClusterCreationStep; - defaultValues?: NewWorkloadClusterConfig; - loading: boolean; - managementCluster?: ManagementCluster; - onClusterDelete: () => void; - onDownloadKubeconfig: () => void; - onMenuClose: () => void; - onSubmit: () => void; -} - -export const CreateClusterFlow: FC = ({ - cluster, - clusterCreationStep, - defaultValues, - loading, - managementCluster, - onClusterDelete, - onDownloadKubeconfig, - onMenuClose, - onSubmit, -}) => { - const { isOpen, close, toggle } = useToggle(); - - const methods = useForm({ - defaultValues, - mode: 'onChange', - }); - - const { - formState: { isValid }, - } = methods; - - const handleClick = useCallback(() => { - if (clusterCreationStep === ClusterCreationStep.DETAILS) { - onClusterDelete(); - } - }, [onClusterDelete, clusterCreationStep]); - - const submitButtonDisabled = - !isValid || - loading || - (cluster?.clusterId !== RESERVED_DRAFT_CLUSTER_NAME && - cluster?.status === ClusterStatus.PROVISIONING); - - const showingClusterDetails = clusterCreationStep === ClusterCreationStep.DETAILS; - - return ( - -
- - - - - - {showingClusterDetails && ( - {cluster?.clusterName} - )} - - - {!showingClusterDetails ? ( - - ) : ( - - - {isOpen && ( - - - - - - Download kubeconfig - - - - - - Delete cluster - - - - - - - )} - - )} - - - {!showingClusterDetails && } - {showingClusterDetails && cluster && ( - - )} - -
-
- ); -}; diff --git a/containers/ContactUsModal/ContactUsForm/ContactUsForm.styled.ts b/containers/ContactUsModal/ContactUsForm/ContactUsForm.styled.ts deleted file mode 100644 index 84b41063..00000000 --- a/containers/ContactUsModal/ContactUsForm/ContactUsForm.styled.ts +++ /dev/null @@ -1,37 +0,0 @@ -import styled from 'styled-components'; -import { Box } from '@mui/material'; - -export const Container = styled(Box)` - width: 606px; -`; - -export const Header = styled.div` - align-items: center; - display: flex; - justify-content: space-between; - padding: 24px; - - & > svg { - cursor: pointer; - } -`; - -export const Footer = styled.div` - display: flex; - justify-content: flex-end; - gap: 16px; - padding: 16px 24px; -`; - -export const NameSection = styled.div` - display: flex; - gap: 24px; -`; - -export const FieldsContainer = styled.div` - display: flex; - flex-direction: column; - padding: 0 24px; - gap: 24px; - margin-bottom: 24px; -`; diff --git a/containers/ContactUsModal/ContactUsForm/ContactUsForm.tsx b/containers/ContactUsModal/ContactUsForm/ContactUsForm.tsx deleted file mode 100644 index fcc014a9..00000000 --- a/containers/ContactUsModal/ContactUsForm/ContactUsForm.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React, { FunctionComponent } from 'react'; -import { useFormContext } from 'react-hook-form'; -import { Divider } from '@mui/material'; -import CloseIcon from '@mui/icons-material/Close'; - -import { Container, FieldsContainer, Footer, Header, NameSection } from './ContactUsForm.styled'; - -import Typography from '@/components/Typography/Typography'; -import { BISCAY, SALTBOX_BLUE, VOLCANIC_SAND } from '@/constants/colors'; -import Button from '@/components/Button/Button'; -import { ContactUsFields, UserRequest } from '@/types/subscription'; -import ControlledTextArea from '@/components/controlledFields/ControlledTextArea/ControlledTextArea'; -import ControlledTextField from '@/components/controlledFields/ControlledTextField/ControlledTextField'; -import { EMAIL_REGEX } from '@/constants'; - -export interface ContactUsFormProps { - closeModal: () => void; - handleContactUsRequest: (userRequest: UserRequest) => void; -} - -const ContactUsFormProps: FunctionComponent = ({ - closeModal, - handleContactUsRequest, -}) => { - const { - control, - handleSubmit, - reset, - formState: { isValid }, - } = useFormContext(); - - const onSubmit = async ({ email, firstName, lastName, message }: ContactUsFields) => { - const requestData: UserRequest = { - requestData: JSON.stringify({ message, email, name: `${firstName} ${lastName}` }), - type: 'upgrade', - }; - - handleContactUsRequest(requestData); - reset(); - }; - - return ( - -
- - Contact us for an Enterprise Plan - - -
- - - Get even more GitOps magic with an Enterprise Plan. Send us a few details and a member of - our team will reach out to you as soon as possible.{' '} - - - - - - - - - - -
- - -
-
- ); -}; - -export default ContactUsFormProps; diff --git a/containers/ContactUsModal/ContactUsModal.tsx b/containers/ContactUsModal/ContactUsModal.tsx deleted file mode 100644 index c889fbe4..00000000 --- a/containers/ContactUsModal/ContactUsModal.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, { FunctionComponent, useCallback } from 'react'; -import { FormProvider, useForm } from 'react-hook-form'; - -import ContactUsForm from './ContactUsForm/ContactUsForm'; - -import { CancelSubscriptionFields, UserRequest } from '@/types/subscription'; -import Modal from '@/components/Modal/Modal'; -import { useAppDispatch } from '@/redux/store'; -import { createUserRequest, validateLicenseKey } from '@/redux/thunks/subscription.thunk'; - -export interface ContactUsProps { - isOpen: boolean; - closeModal: () => void; -} - -const ContactUs: FunctionComponent = ({ isOpen, closeModal }) => { - const dispatch = useAppDispatch(); - - const methods = useForm({ - mode: 'onChange', - }); - - const handleContactUsRequest = useCallback( - (userRequest: UserRequest) => { - dispatch(createUserRequest(userRequest)).then(() => { - dispatch(validateLicenseKey()); - closeModal(); - }); - }, - [closeModal, dispatch], - ); - - return ( - - - - - - ); -}; - -export default ContactUs; diff --git a/containers/Environments/Environments.tsx b/containers/Environments/Environments.tsx deleted file mode 100644 index 4077c52e..00000000 --- a/containers/Environments/Environments.tsx +++ /dev/null @@ -1,169 +0,0 @@ -'use client'; -import React, { FunctionComponent, useEffect, useState, useCallback } from 'react'; -import styled from 'styled-components'; -import Image from 'next/image'; - -import Row from '@/components/Row/Row'; -import Column from '@/components/Column/Column'; -import Typography from '@/components/Typography/Typography'; -import LearnMore from '@/components/LearnMore/LearnMore'; -import Button from '@/components/Button/Button'; -import compDisplayImage from '@/assets/comp_display.svg'; -import useToggle from '@/hooks/useToggle'; -import Modal from '@/components/Modal/Modal'; -import { CreateEnvironmentMenu } from '@/components/CreateEnvironmentMenu/CreateEnvironmentMenu'; -import { useAppDispatch, useAppSelector } from '@/redux/store'; -import { - createEnvironment, - deleteEnvironment, - getAllEnvironments, -} from '@/redux/thunks/environments.thunk'; -import EnvironmentsTable from '@/components/EnvironmentsTable/EnvironmentsTable'; -import { ClusterEnvironment } from '@/types/provision'; -import DeleteEnvironment from '@/components/DeleteEnvironment/DeleteEnvironment'; -import { clearEnvironmentError } from '@/redux/slices/environments.slice'; -import { noop } from '@/utils/noop'; -import { VOLCANIC_SAND } from '@/constants/colors'; -import { DOCS_LINK } from '@/constants'; - -const Environments: FunctionComponent = () => { - const { isOpen, close, open } = useToggle(); - - const [selectedEnv, setSelectedEnv] = useState(); - - const { environments, boundEnvironments, error } = useAppSelector( - ({ environments }) => environments, - ); - const dispatch = useAppDispatch(); - - const handleAddEnvironment = useCallback( - (env: ClusterEnvironment) => { - dispatch(createEnvironment(env)) - .unwrap() - .then(() => { - close(); - }) - .catch(noop); - }, - [dispatch, close], - ); - - const handleEnvironmentDelete = useCallback(() => { - if (selectedEnv) { - dispatch(deleteEnvironment(selectedEnv)) - .unwrap() - .then(() => { - setSelectedEnv(undefined); - }) - .catch(noop); - } - }, [selectedEnv, dispatch]); - - const handleDeleteModal = useCallback(() => { - setSelectedEnv(undefined); - }, []); - - const handleErrorClose = useCallback(() => { - dispatch(clearEnvironmentError()); - }, [dispatch]); - - const handleModalClose = useCallback(() => { - dispatch(clearEnvironmentError()); - close(); - }, [dispatch, close]); - - const handleDeleteEnv = useCallback( - (id: string) => { - if (environments[id]) { - setSelectedEnv(environments[id]); - } - }, - [environments], - ); - - useEffect(() => { - dispatch(getAllEnvironments()); - }, [dispatch]); - - return ( - -
- - Environments - - - -
- - {Object.keys(environments).length ? ( - - ) : ( - - computer display - - You don't have any environments defined - - - Create and add environments for your application development. - - - )} - - - - {selectedEnv && ( - - )} -
- ); -}; - -export default Environments; - -const Header = styled(Row)` - margin: 40px; - justify-content: space-between; -`; - -const HeadingContainer = styled(Column)` - gap: 8px; - - ${LearnMore} { - color: ${VOLCANIC_SAND}; - } -`; - -const DisplayContainer = styled(Column)` - width: 100%; - align-items: center; - margin-top: 110px; - gap: 16px; -`; diff --git a/containers/GitAccount/GitAccount.styled.ts b/containers/GitAccount/GitAccount.styled.ts deleted file mode 100644 index 1a8a3b96..00000000 --- a/containers/GitAccount/GitAccount.styled.ts +++ /dev/null @@ -1,70 +0,0 @@ -import styled from 'styled-components'; - -import Column from '@/components/Column/Column'; -import LearnMore from '@/components/LearnMore/LearnMore'; -import Row from '@/components/Row/Row'; -import { TRAFFIC_WHITE, VOLCANIC_SAND, WASH_ME } from '@/constants/colors'; -import { media } from '@/utils/media'; - -export const Form = styled.form` - display: flex; - flex-direction: column; - flex: 1; - padding: 32px 40px; - gap: 24px; - overflow-y: auto; -`; - -export const FormFooter = styled(Row)` - padding: 16px 24px; - border-top: 2px solid ${WASH_ME}; - justify-content: flex-end; - gap: 16px; -`; - -export const FormHeader = styled(Row)` - padding: 16px 24px; - border-bottom: 2px solid ${WASH_ME}; - justify-content: space-between; - align-items: center; - height: 40px; -`; - -export const Header = styled(Row)` - justify-content: space-between; -`; - -export const HeadingContainer = styled(Column)` - gap: 8px; - - ${LearnMore} { - color: ${VOLCANIC_SAND}; - } -`; - -export const GitFieldsContainer = styled(Column)` - gap: 12px; - flex: 1; - - ${media.greaterThan('sm')` - flex-direction: row; - `} -`; - -export const GitUserField = styled(Column)` - width: 100%; - max-width: 312px; -`; - -export const GitUserFieldInput = styled(Row)` - background-color: ${TRAFFIC_WHITE}; - border-radius: 4px; - color: ${({ theme }) => theme.colors.volcanicSand}; - height: 20px; - margin-top: 8px; - padding: 8px 12px; - overflow: ellipsis; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -`; diff --git a/containers/GitAccount/GitAccount.tsx b/containers/GitAccount/GitAccount.tsx deleted file mode 100644 index 47b20a45..00000000 --- a/containers/GitAccount/GitAccount.tsx +++ /dev/null @@ -1,199 +0,0 @@ -'use client'; -import React, { FunctionComponent, useCallback, useMemo } from 'react'; -import { useForm } from 'react-hook-form'; - -import { - Form, - FormFooter, - FormHeader, - GitFieldsContainer, - GitUserField, - GitUserFieldInput, - Header, - HeadingContainer, -} from './GitAccount.styled'; - -import Typography from '@/components/Typography/Typography'; -import LearnMore from '@/components/LearnMore/LearnMore'; -import { useAppSelector } from '@/redux/store'; -import FormContainer from '@/components/FormContainer/FormContainer'; -import Column from '@/components/Column/Column'; -import Row from '@/components/Row/Row'; -import Button from '@/components/Button/Button'; -import useToggle from '@/hooks/useToggle'; -import GitProviderLabel from '@/components/GitProviderLabel/GitProviderLabel'; -import { GitProvider } from '@/types'; -import { EXCLUSIVE_PLUM, VOLCANIC_SAND } from '@/constants/colors'; -import ControlledPassword from '@/components/controlledFields/ControlledPassword'; -import { GIT_PROVIDER_DISPLAY_NAME } from '@/constants'; -import Tooltip from '@/components/Tooltip/Tooltip'; -import ControlledTextField from '@/components/controlledFields/ControlledTextField/ControlledTextField'; - -type GitAccountFields = { - gitToken?: string; - gitProvider?: string; - gitOrg?: string; - gitRepo?: string; -}; - -const GitAccount: FunctionComponent = () => { - const { managementCluster, githubUser, gitlabUser } = useAppSelector(({ api, git }) => ({ - ...api, - ...git, - })); - - const { isOpen, toggle } = useToggle(); - - const { - control, - handleSubmit, - formState: { errors }, - reset, - } = useForm({ - values: { - gitToken: managementCluster?.gitAuth.gitToken, - gitProvider: managementCluster?.gitProvider, - gitOrg: managementCluster?.gitAuth.gitOwner, - gitRepo: managementCluster?.gitHost, - }, - mode: 'onSubmit', - }); - - const handleFormSubmit = useCallback(() => { - // TODO: Handle submit - }, []); - - const handleEdit = useCallback(() => { - reset(undefined, { keepErrors: false, keepDirty: false }); - toggle(); - }, [reset, toggle]); - - const gitUserName = useMemo( - () => githubUser?.login || gitlabUser?.name, - [githubUser, gitlabUser], - ); - - const gitLabel = useMemo( - () => GIT_PROVIDER_DISPLAY_NAME[managementCluster?.gitProvider as GitProvider], - [managementCluster?.gitProvider], - ); - - const showTooltip = useMemo(() => (gitUserName?.length ?? 0) > 22, [gitUserName]); - - return ( -
-
- - - Git account - - - -
- - - Account details - - {!isOpen && ( - - )} - - } - footerContent={ - isOpen ? ( - - - - - ) : null - } - > - - - - Git provider - - - - - - - - - - {`Username associated with ${gitLabel} token`} - {showTooltip ? ( - - {gitUserName} - - ) : ( - {gitUserName} - )} - - - - - - - - -
- ); -}; - -export default GitAccount; diff --git a/containers/GitOpsCatalog/GitOpsCatalog.styled.ts b/containers/GitOpsCatalog/GitOpsCatalog.styled.ts deleted file mode 100644 index bce7c5c9..00000000 --- a/containers/GitOpsCatalog/GitOpsCatalog.styled.ts +++ /dev/null @@ -1,36 +0,0 @@ -'use client'; -import styled from 'styled-components'; - -import Row from '@/components/Row/Row'; -import Column from '@/components/Column/Column'; - -export const CardsContainer = styled(Row)` - flex-wrap: wrap; - gap: 16px; -`; - -export const Container = styled(Row)` - height: calc(100% - 80px); -`; - -export const Content = styled.div` - height: calc(100% - 30px); - padding: 0 24px; - width: 100%; -`; - -export const Filter = styled.div` - background: ${({ theme }) => theme.colors.white}; - border-width: 1px 1px 0px 1px; - border-style: solid; - border-color: ${({ theme }) => theme.colors.pastelLightBlue}; - border-radius: 8px; - height: 100vh; - overflow: auto; - padding: 24px 24px 0 24px; - width: 266px; -`; - -export const CardsByCategory = styled(Column)` - gap: 24px; -`; diff --git a/containers/GitOpsCatalog/GitOpsCatalog.tsx b/containers/GitOpsCatalog/GitOpsCatalog.tsx deleted file mode 100644 index 8bbcd246..00000000 --- a/containers/GitOpsCatalog/GitOpsCatalog.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import React, { FunctionComponent, useCallback, useMemo } from 'react'; -import { useSession } from 'next-auth/react'; -import { useForm } from 'react-hook-form'; -import NextLink from 'next/link'; -import sortBy from 'lodash/sortBy'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import FormGroup from '@mui/material/FormGroup'; - -import { CardsContainer, Container, Content, Filter } from './GitOpsCatalog.styled'; - -import Checkbox from '@/components/Checkbox/Checkbox'; -import Typography from '@/components/Typography/Typography'; -import GitOpsCatalogCard from '@/components/GitOpsCatalogCard/GitOpsCatalogCard'; -import GitopsAppModal from '@/components/GitOpsAppModal/GitOpsAppModal'; -import useModal from '@/hooks/useModal'; -import { useAppDispatch, useAppSelector } from '@/redux/store'; -import { installGitOpsApp } from '@/redux/thunks/applications.thunk'; -import { AppCategory, GitOpsCatalogApp } from '@/types/applications'; -import { VOLCANIC_SAND } from '@/constants/colors'; -import { - addCategory, - removeCategory, - setSelectedCatalogApp, -} from '@/redux/slices/applications.slice'; - -const STATIC_HELP_CARD: GitOpsCatalogApp = { - name: '', - display_name: 'Can’t find what you need?', - image_url: 'https://assets.kubefirst.com/console/help.png', -}; - -interface GitOpsCatalogProps { - catalogApplications: GitOpsCatalogApp[]; -} - -const GitOpsCatalog: FunctionComponent = ({ catalogApplications }) => { - const { - appsQueue, - gitOpsCatalogApps, - selectedCategories, - clusterApplications, - selectedCatalogApp, - filter, - } = useAppSelector(({ applications }) => applications); - - const { data: session } = useSession(); - - const { isOpen, openModal, closeModal } = useModal(); - - const { - control, - formState: { isValid }, - getValues, - reset, - } = useForm(); - - const dispatch = useAppDispatch(); - - const handleCategoryClick = useCallback( - (category: AppCategory) => { - const isCategorySelected = selectedCategories.includes(category); - - if (isCategorySelected) { - dispatch(removeCategory(category)); - } else { - dispatch(addCategory(category)); - } - }, - [selectedCategories, dispatch], - ); - - const handleAddApp = useCallback(() => { - const values = getValues(); - - dispatch( - installGitOpsApp({ - values, - user: (session?.user?.email as string) || 'kbot', - }), - ); - reset(); - closeModal(); - }, [dispatch, closeModal, getValues, reset, session]); - - const handleSelectedApp = useCallback( - (app: GitOpsCatalogApp) => { - dispatch(setSelectedCatalogApp(app)); - - if (app.secret_keys?.length || app.config_keys?.length) { - openModal(); - } else { - handleAddApp(); - } - }, - [dispatch, handleAddApp, openModal], - ); - - const uninstalledCatalogApps = useMemo( - () => - gitOpsCatalogApps.filter((app) => !clusterApplications.map((s) => s.name).includes(app.name)), - [clusterApplications, gitOpsCatalogApps], - ); - - const sortedAvailableCategories = useMemo( - () => - uninstalledCatalogApps.reduce((previous, current) => { - if (current.category && !previous.includes(current.category)) { - previous.push(current.category); - } - return sortBy(previous); - }, []), - [uninstalledCatalogApps], - ); - - return ( - - - - Category - - {sortedAvailableCategories.map((category) => ( - - handleCategoryClick(category)} />} - label={ - - {category} - - } - sx={{ ml: 0 }} - /> - - ))} - - - - {catalogApplications.map((app) => ( - handleSelectedApp(app)} - isInstalling={appsQueue.includes(app.name)} - isDisabled={!filter.cluster} - /> - ))} - - <> - To suggest an open source app that installs to your cluster, discuss your idea via an{' '} - - issue - - . Learn how you can do this on our{' '} - - Contributing file - - . -
-
- Alternatively contact us via our{' '} - - Slack Community - {' '} - in the #helping-hands or #contributors channels. - -
-
-
- {isOpen && selectedCatalogApp && ( - - )} -
- ); -}; - -export default GitOpsCatalog; diff --git a/containers/Header/Header.tsx b/containers/Header/Header.tsx index 65c9332c..18333a34 100644 --- a/containers/Header/Header.tsx +++ b/containers/Header/Header.tsx @@ -1,7 +1,5 @@ 'use client'; import React, { FunctionComponent, useMemo } from 'react'; -import { useSession } from 'next-auth/react'; -import { signOut } from 'next-auth/react'; import HelpOutlineOutlinedIcon from '@mui/icons-material/HelpOutlineOutlined'; import MenuBookOutlinedIcon from '@mui/icons-material/MenuBookOutlined'; import List from '@mui/material/List'; @@ -9,31 +7,20 @@ import ListItem from '@mui/material/ListItem'; import { ClickAwayListener, ListItemButton } from '@mui/material'; import Image from 'next/image'; import VideogameAssetOutlinedIcon from '@mui/icons-material/VideogameAssetOutlined'; -import LogoutIcon from '@mui/icons-material/Logout'; import { BsSlack } from 'react-icons/bs'; -import { Avatar, Container, Menu, ProfileMenu } from './Header.styled'; +import { Container, Menu } from './Header.styled'; import { noop } from '@/utils/noop'; import Youtube from '@/assets/youtube-dark.svg'; import { useAppSelector } from '@/redux/store'; import Typography from '@/components/Typography/Typography'; -import { ECHO_BLUE, PRIMARY, TRAFFIC_WHITE, VOLCANIC_SAND } from '@/constants/colors'; +import { ECHO_BLUE, PRIMARY, VOLCANIC_SAND } from '@/constants/colors'; import useToggle from '@/hooks/useToggle'; import useFeatureFlag from '@/hooks/useFeatureFlag'; import { FeatureFlag } from '@/types/config'; import { DOCS_LINK } from '@/constants'; -function stringAvatar(name?: string | null) { - return { - sx: { - bgcolor: TRAFFIC_WHITE, - color: '#94A3B8', - }, - children: `${name && name[0]}`, - }; -} - export interface HeaderProps { handleOpenFlappy: typeof noop; handleOpenKubefirstModal: typeof noop; @@ -41,9 +28,7 @@ export interface HeaderProps { const Header: FunctionComponent = ({ handleOpenFlappy, handleOpenKubefirstModal }) => { const { isOpen: isHelpMenuOpen, open, close } = useToggle(); - const { isOpen: isProfileMenuOpen, open: openProfileMenu, close: closeProfileMenu } = useToggle(); - const { data: session } = useSession(); const { isClusterZero } = useAppSelector(({ api, config }) => ({ isClusterZero: config.isClusterZero, managementCluster: api.managementCluster, @@ -126,37 +111,6 @@ const Header: FunctionComponent = ({ handleOpenFlappy, handleOpenKu )} )} - {session?.user && ( - - )} - {isProfileMenuOpen && ( - - - - signOut()} - > - - - - Logout - - - - - - - )} ); }; diff --git a/containers/Layout/Layout.tsx b/containers/Layout/Layout.tsx index 05041b62..30bb5a38 100644 --- a/containers/Layout/Layout.tsx +++ b/containers/Layout/Layout.tsx @@ -1,52 +1,34 @@ 'use client'; import React, { PropsWithChildren, useEffect, useState } from 'react'; -import { Typography } from '@mui/material'; import KubefirstContent from '../KubefirstContent/KubefirstContent'; -import { Link, Container, Content } from './Layout.styled'; +import { Container, Content } from './Layout.styled'; import Header from '@/containers/Header/Header'; import Navigation from '@/containers/Navigation/Navigation'; -import { useAppDispatch, useAppSelector } from '@/redux/store'; -import { getClusters } from '@/redux/thunks/api.thunk'; -import { License } from '@/types/subscription'; -import { setLicense } from '@/redux/slices/subscription.slice'; +import { useAppDispatch } from '@/redux/store'; import { EnvironmentVariables, FeatureFlag } from '@/types/config'; import { setFlags } from '@/redux/slices/featureFlags.slice'; import { setConfigValues } from '@/redux/slices/config.slice'; import FlappyKray from '@/components/FlappyKRay/FlappyKRay'; import useModal from '@/hooks/useModal'; -import { - selectHasLicenseKey, - selectIsLicenseActive, - selectPendingInvoice, -} from '@/redux/selectors/subscription.selector'; -import Banner from '@/components/Banner/Banner'; -import { WHITE } from '@/constants/colors'; -import { setIsBannerOpen } from '@/redux/slices/settings.slice'; export interface LayoutProps extends PropsWithChildren { - license: License; envVariables: EnvironmentVariables; featureFlags: Record; } -export function Layout({ children, envVariables, featureFlags, license }: LayoutProps) { +export function Layout({ children, envVariables, featureFlags }: LayoutProps) { const [loadFlags, setLoadFlags] = useState(false); const { isOpen, openModal, closeModal } = useModal(); + const dispatch = useAppDispatch(); const { isOpen: isModalContentOpen, openModal: openModalContent, closeModal: closeModalContent, } = useModal(); - const dispatch = useAppDispatch(); - const isBannerOpen = useAppSelector(({ settings }) => settings.isBannerOpen); - const hasLicenseKey = useAppSelector(selectHasLicenseKey()); - const isLicenseActive = useAppSelector(selectIsLicenseActive()); - const pendingInvoice = useAppSelector(selectPendingInvoice()); - const handleOpenFlappy = () => { openModal(); }; @@ -55,21 +37,11 @@ export function Layout({ children, envVariables, featureFlags, license }: Layout openModalContent(); }; - const handleCloseBanner = () => { - dispatch(setIsBannerOpen(false)); - }; - useEffect(() => { setLoadFlags(true); dispatch(setFlags(featureFlags)); dispatch(setConfigValues(envVariables)); - dispatch(setLicense(license)); - dispatch(getClusters()); - }, [dispatch, envVariables, featureFlags, license, loadFlags]); - - useEffect(() => { - dispatch(setIsBannerOpen(hasLicenseKey && !isLicenseActive)); - }, [dispatch, hasLicenseKey, isLicenseActive]); + }, [dispatch, envVariables, featureFlags, loadFlags]); return ( @@ -78,18 +50,6 @@ export function Layout({ children, envVariables, featureFlags, license }: Layout handleOpenKubefirstModal={handleOpenKubefirstModal} /> - {isBannerOpen && ( - - - Your payment was declined. Please{' '} - - update your billing information - {' '} - to continue to use the kubefirst UI to manage your physical clusters. Alternatively, - manage your physical clusters directly in your GitOps repository on a Free Plan. - - - )}
` - border-radius: 8px; - width: auto; - - ${({ hasMargin }) => - hasMargin && - ` - &:last-child { - margin-top: 32px; - } - `} -`; - -export const BottomFormContainer = styled(FormContainer)<{ hasMargin: boolean }>` - align-items: center; - border-radius: 0 0 8px 8px; - border-top: 1px solid #e5e5e5; - display: flex; - justify-content: space-between; - flex-direction: row; - width: 100%; - - & > div { - align-items: center; - display: flex; - flex-direction: row; - justify-content: space-between; - width: 100%; - } -`; - -export const UList = styled.ul` - padding-left: 20px; -`; - -export const CancelContainer = styled.div` - align-items: flex-start; - background: #fff7ed; - border: 1px solid #ffedd5; - border-radius: 4px; - display: flex; - gap: 8px; - margin-bottom: 8px; - padding: 16px; -`; diff --git a/containers/License/License.tsx b/containers/License/License.tsx deleted file mode 100644 index dfece71e..00000000 --- a/containers/License/License.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import React, { FunctionComponent, useMemo } from 'react'; -import moment from 'moment'; -import { Box, CircularProgress } from '@mui/material'; -import { FieldValues, useFormContext } from 'react-hook-form'; -import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; - -import { BottomFormContainer, CancelContainer, FormContainer, UList } from './License.styled'; - -import { COAL_MINE, EXCLUSIVE_PLUM, ORANGEALICIOUS, VOLCANIC_SAND } from '@/constants/colors'; -import Button from '@/components/Button/Button'; -import LearnMore from '@/components/LearnMore/LearnMore'; -import Typography from '@/components/Typography/Typography'; -import { useAppSelector } from '@/redux/store'; -import ControlledPassword from '@/components/controlledFields/ControlledPassword'; -import { selectHasLicenseKey, selectRequestByType } from '@/redux/selectors/subscription.selector'; - -export interface LicenseProps { - handleActivateLicense: (licenseKey: string) => void; - handleCancelSubscription: () => void; -} - -const License: FunctionComponent = ({ - handleActivateLicense, - handleCancelSubscription, -}) => { - const { isLoading, license, error } = useAppSelector(({ subscription }) => subscription); - - const hasLicenseKey = useAppSelector(selectHasLicenseKey()); - const cancelRequest = useAppSelector(selectRequestByType('cancel')); - - const formattedLicenseKey = useMemo( - () => - license?.licenseKey && - `${license?.licenseKey.slice(0, 4).toUpperCase()}************************************`, - [license?.licenseKey], - ); - - const { - control, - handleSubmit, - formState: { isValid }, - } = useFormContext(); - - const onSubmit = async ({ licenseKey }: FieldValues) => { - handleActivateLicense(licenseKey); - }; - - return ( - - - - {!hasLicenseKey && ( - - )} - - } - > - {`You are on a ${ - license?.plan?.name || 'Community' - } Plan`} - {hasLicenseKey ? ( - <> - - License key - - - {formattedLicenseKey} - - - ) : ( - - )} - - {hasLicenseKey && ( - <> - - - - - } - > - - Cancel my subscription - - {!!cancelRequest?.id && ( - - - - {`Your request to cancel your subscription was submitted ${moment( - cancelRequest.createdAt, - ).format('DD MMM YYYY, HH:MM:SS')}`} - - - )} - - - What to expect: - -
  • You will be downgraded to a Free Plan.
  • -
  • - You will no longer be able to view and manage any physical clusters you may have - provisioned, this will only by possible via the kubefirst CLI. -
  • -
  • We will send you an email confirming that your account has been downgraded.
  • -
  • You will still have access until the end of your current billing cycle.
  • -
    -
    -
    - - )} -
    - ); -}; - -export default License; diff --git a/containers/Navigation/Navigation.tsx b/containers/Navigation/Navigation.tsx index 36cfab08..c9c424fd 100644 --- a/containers/Navigation/Navigation.tsx +++ b/containers/Navigation/Navigation.tsx @@ -1,25 +1,11 @@ 'use client'; -import React, { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'; +import React, { FunctionComponent, useCallback, useEffect, useState } from 'react'; import { usePathname } from 'next/navigation'; -import { BsSlack } from 'react-icons/bs'; -// import { FaGitAlt } from 'react-icons/fa'; -import ScatterPlotIcon from '@mui/icons-material/ScatterPlot'; -import GridViewOutlinedIcon from '@mui/icons-material/GridViewOutlined'; -import CollectionsOutlinedIcon from '@mui/icons-material/CollectionsOutlined'; -import ReceiptLongIcon from '@mui/icons-material/ReceiptLong'; -import HelpOutlineOutlinedIcon from '@mui/icons-material/HelpOutlineOutlined'; -import StarBorderOutlinedIcon from '@mui/icons-material/StarBorderOutlined'; -import NavigationComponent, { FooterItem } from '@/components/Navigation/Navigation'; +import NavigationComponent from '@/components/Navigation/Navigation'; import { useAppSelector } from '@/redux/store'; import { noop } from '@/utils/noop'; import { selectConfig } from '@/redux/selectors/config.selector'; -import useFeatureFlag from '@/hooks/useFeatureFlag'; -import { InstallationType } from '@/types/redux'; -import { FeatureFlag } from '@/types/config'; -import { ASMANI_SKY } from '@/constants/colors'; -import { SaasPlans } from '@/types/subscription'; -import { DOCS_LINK, Route } from '@/constants'; export interface NavigationProps { handleOpenFlappy: typeof noop; @@ -33,98 +19,7 @@ const Navigation: FunctionComponent = ({ const [domLoaded, setDomLoaded] = useState(false); const asPath = usePathname(); - const { kubefirstVersion, isClusterZero } = useAppSelector(selectConfig()); - const { managementCluster } = useAppSelector(({ api }) => ({ - managementCluster: api.managementCluster, - })); - const license = useAppSelector(({ subscription }) => subscription.license); - - const { isEnabled: isMultiClusterEnabled } = useFeatureFlag(FeatureFlag.MULTICLUSTER_MANAGEMENT); - const { isEnabled: isSubscriptionEnabled } = useFeatureFlag(FeatureFlag.SAAS_SUBSCRIPTION); - - const routes = useMemo( - () => - [ - { - icon: , - path: Route.CLUSTER_MANAGEMENT, - title: 'Cluster Management', - isEnabled: - isMultiClusterEnabled && - !isClusterZero && - managementCluster?.cloudProvider !== InstallationType.LOCAL, - }, - { - icon: , - path: Route.APPLICATIONS, - title: 'Applications', - isEnabled: !isClusterZero, - }, - { - icon: , - path: Route.ENVIRONMENTS, - title: 'Environments', - isEnabled: !isClusterZero && managementCluster?.cloudProvider !== InstallationType.LOCAL, - }, - { - icon: , - path: Route.SUBSCRIPTION, - title: 'Subscription', - group: 'Admin settings', - groupOrder: 2, - isEnabled: !isClusterZero && isSubscriptionEnabled, - }, - // { - // icon: , - // path: Route.GIT_ACCOUNT, - // title: 'Git account', - // group: 'Admin settings', - // groupOrder: 3, - // isEnabled: !isClusterZero && managementCluster?.cloudProvider !== InstallationType.LOCAL, - // }, - ].filter(({ isEnabled }) => isEnabled), - [isMultiClusterEnabled, isClusterZero, managementCluster?.cloudProvider, isSubscriptionEnabled], - ); - - const nextLicenseUpgradeTitle = useMemo(() => { - if (!license?.licenseKey) { - return 'Upgrade to Pro'; - } - - if (license?.plan?.name === SaasPlans.Pro) { - return 'Upgrade to Enterprise'; - } - - return undefined; - }, [license?.licenseKey, license?.plan?.name]); - - const footerItems = useMemo( - () => - isSubscriptionEnabled && !isClusterZero - ? nextLicenseUpgradeTitle && [ - { - icon: , - path: Route.SUBSCRIPTION_PLANS, - title: nextLicenseUpgradeTitle, - color: ASMANI_SKY, - }, - ] - : [ - { - icon: , - path: DOCS_LINK, - title: 'Documentation', - color: '', - }, - { - icon: , - path: 'http://konstruct.io/slack', - title: 'Slack', - color: '', - }, - ], - [isClusterZero, isSubscriptionEnabled, nextLicenseUpgradeTitle], - ); + const { kubefirstVersion } = useAppSelector(selectConfig()); const handleIsActiveItem = useCallback( (route: string) => { @@ -150,12 +45,11 @@ const Navigation: FunctionComponent = ({ } - isSubscriptionEnabled={isSubscriptionEnabled} + footerItems={[]} /> ); }; diff --git a/containers/Provision/Provision.tsx b/containers/Provision/Provision.tsx index d7c7d761..0382334c 100644 --- a/containers/Provision/Provision.tsx +++ b/containers/Provision/Provision.tsx @@ -20,18 +20,17 @@ import ErrorBanner from '@/components/ErrorBanner/ErrorBanner'; import Button from '@/components/Button/Button'; import { clearError, - setError, setInstallType, setInstallValues, setInstallationStep, } from '@/redux/slices/installation.slice'; import { clearClusterState, clearValidation } from '@/redux/slices/api.slice'; import { useAppDispatch, useAppSelector } from '@/redux/store'; -import { createCluster, resetClusterProgress } from '@/redux/thunks/api.thunk'; +import { createCluster, getCloudRegions, resetClusterProgress } from '@/redux/thunks/api.thunk'; import { useInstallation } from '@/hooks/useInstallation'; import { InstallValues, InstallationType } from '@/types/redux'; import { GitProvider } from '@/types'; -import { AUTHENTICATION_ERROR_MSG, DEFAULT_CLOUD_INSTANCE_SIZES, DOCS_LINK } from '@/constants'; +import { DEFAULT_CLOUD_INSTANCE_SIZES, DOCS_LINK } from '@/constants'; import { useQueue } from '@/hooks/useQueue'; import LearnMore from '@/components/LearnMore/LearnMore'; @@ -174,6 +173,13 @@ const Provision: FunctionComponent = () => { if (isValid) { dispatch(setInstallValues(values)); + // this step validates the authentication provided + if (isAuthStep) { + return dispatch( + getCloudRegions({ installType: installType as InstallationType, values, validate: true }), + ); + } + if (isSetupStep) { try { await provisionCluster(); @@ -275,14 +281,6 @@ const Provision: FunctionComponent = () => { href, ]); - useEffect(() => { - if (isAuthStep && isAuthenticationValid === false) { - dispatch(setError({ error: AUTHENTICATION_ERROR_MSG })); - } else if (isAuthStep && isAuthenticationValid) { - handleGoNext(); - } - }, [dispatch, handleGoNext, isAuthStep, isAuthenticationValid]); - useEffect(() => { if (isMarketplace && installMethod) { const [cloud] = installMethod.split('-') || ['']; diff --git a/containers/Subscription/Subscription.styled.ts b/containers/Subscription/Subscription.styled.ts deleted file mode 100644 index d7776280..00000000 --- a/containers/Subscription/Subscription.styled.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { styled } from 'styled-components'; - -import Column from '@/components/Column/Column'; - -export const Container = styled(Column)` - margin: 40px; -`; - -export const PlansContainer = styled.div` - align-items: center; - display: flex; - gap: 40px; - height: 100%; -`; diff --git a/containers/Subscription/Subscription.tsx b/containers/Subscription/Subscription.tsx deleted file mode 100644 index 18264404..00000000 --- a/containers/Subscription/Subscription.tsx +++ /dev/null @@ -1,152 +0,0 @@ -'use client'; -import React, { FunctionComponent, useEffect, useMemo } from 'react'; -import Box from '@mui/material/Box'; -import Tabs from '@mui/material/Tabs'; -import { FormProvider, useForm } from 'react-hook-form'; - -import License from '../License/License'; -import CancelSubscription from '../CancelSubscription/CancelSubscription'; -import Billing from '../Billing/Billing'; -import ContactUs from '../ContactUsModal/ContactUsModal'; - -import { Container, PlansContainer } from './Subscription.styled'; - -import TabPanel, { Tab, a11yProps } from '@/components/Tab/Tab'; -import { BISCAY, VOLCANIC_SAND } from '@/constants/colors'; -import { useAppDispatch, useAppSelector } from '@/redux/store'; -import Typography from '@/components/Typography/Typography'; -import { setActiveTab } from '@/redux/slices/settings.slice'; -import { SettingsTab, SettingsTabMap } from '@/constants/setttings'; -import { Plan } from '@/types/plan'; -import { activateLicenseKey, validateLicenseKey } from '@/redux/thunks/subscription.thunk'; -import useModal from '@/hooks/useModal'; -import { SaasPlans } from '@/types/subscription'; -import { selectHasLicenseKey } from '@/redux/selectors/subscription.selector'; -import Pricing from '@/components/Pricing/Pricing'; - -interface SubscriptionProps { - activeTabParam?: string; - plans: Array; -} - -const Subscription: FunctionComponent = ({ activeTabParam, plans }) => { - const dispatch = useAppDispatch(); - const { isOpen, closeModal, openModal } = useModal(); - const { - isOpen: isContactUsModalOpen, - closeModal: closeContactUsModal, - openModal: openContactUsModal, - } = useModal(); - - const { activeTab, license, saasURL } = useAppSelector(({ settings, subscription, config }) => ({ - activeTab: settings.activeTab, - license: subscription.license, - saasURL: config.saasURL, - })); - - const methods = useForm<{ licenseKey: string }>({ - mode: 'onChange', - defaultValues: { - licenseKey: license?.licenseKey || '', - }, - }); - - const currentPlanIndex = useMemo( - () => plans.findIndex(({ name }) => name === license?.plan?.name), - [license, plans], - ); - - const handleOnChangeTab = (event: React.SyntheticEvent, tabIndex: number) => { - dispatch(setActiveTab(tabIndex)); - }; - - const handleActivateLicense = (licenseKey: string) => { - dispatch(activateLicenseKey(licenseKey)).then(() => dispatch(validateLicenseKey())); - }; - - const handleRedirectToSaas = (plan: string) => { - if (plan === SaasPlans.Enterprise && hasLicenseKey) { - openContactUsModal(); - } else { - window.open(`${saasURL}?plan=${plan}`, '_blank'); - } - }; - - const isActivePlan = (plan: string): boolean => { - if (!license?.licenseKey && plan === SaasPlans.Community) { - return true; - } - - return license?.plan?.name === plan; - }; - - const hasLicenseKey = useAppSelector(selectHasLicenseKey()); - - useEffect(() => { - if (activeTabParam) { - dispatch(setActiveTab(SettingsTabMap[activeTabParam])); - } else if (hasLicenseKey) { - dispatch(setActiveTab(SettingsTab.LICENSE_KEY)); - } - }, [activeTabParam, dispatch, hasLicenseKey]); - - return ( - - - Subscription - - <> - - - Billing} - {...a11yProps(SettingsTab.BILLING)} - sx={{ textTransform: 'initial', marginRight: '24px' }} - /> - License key} - {...a11yProps(SettingsTab.LICENSE_KEY)} - sx={{ textTransform: 'initial', marginRight: '24px' }} - /> - Plans} - {...a11yProps(SettingsTab.PLANS)} - sx={{ textTransform: 'initial', marginRight: 0 }} - /> - - - - - - - - - - - - - {plans.map((plan, index) => ( - index} - isActive={isActivePlan(plan.name)} - onClick={() => handleRedirectToSaas(plan.name)} - /> - ))} - - - - - - - ); -}; - -export default Subscription; diff --git a/custom.d.ts b/custom.d.ts index 5f20f8a6..10574166 100644 --- a/custom.d.ts +++ b/custom.d.ts @@ -157,3 +157,20 @@ declare module 'next-auth' { groups: Array; } } + +declare namespace NodeJS { + export interface ProcessEnv { + readonly API_URL: string; + readonly CLUSTER_ID: string; + readonly CLUSTER_TYPE: string; + readonly DISABLE_TELEMETRY: string; + readonly INSTALL_METHOD: string; + readonly IS_CLUSTER_ZERO: string; + readonly DISABLE_AUTH: string; + readonly KUBEFIRST_VERSION: string; + readonly POSTHOG_KEY: string; + readonly CLIENT_ID: string; + readonly SECRET_ID: string; + readonly DOMAIN: string; + } +} diff --git a/environment.d.ts b/environment.d.ts deleted file mode 100644 index 7964b145..00000000 --- a/environment.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -declare namespace NodeJS { - export interface ProcessEnv { - readonly API_URL: string; - readonly CLUSTER_ID: string; - readonly CLUSTER_TYPE: string; - readonly DISABLE_TELEMETRY: string; - readonly INSTALL_METHOD: string; - readonly IS_CLUSTER_ZERO: string; - readonly DISABLE_AUTH: string; - readonly KUBEFIRST_VERSION: string; - readonly POSTHOG_KEY: string; - readonly CLIENT_ID: string; - readonly SECRET_ID: string; - readonly DOMAIN: string; - readonly SAAS_URL: string; - } -} diff --git a/hooks/__tests__/usePaywall.test.ts b/hooks/__tests__/usePaywall.test.ts deleted file mode 100644 index 4a9dd664..00000000 --- a/hooks/__tests__/usePaywall.test.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { renderHook } from '@testing-library/react'; - -import usePaywall from '../usePaywall'; -// eslint-disable-next-line import/order -import { SaasFeatures, SaasPlans } from '../../types/subscription'; - -jest.mock('@/redux/store', () => ({ - useAppSelector: jest.fn(), -})); - -import { useAppSelector } from '../../redux/store'; - -describe('usePaywall', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should return the correct plan', () => { - const mockState = { - api: { clusterMap: {} }, - subscription: { - license: { plan: { name: SaasPlans.Pro }, is_active: true, clusters: [] }, - }, - }; - - (useAppSelector as jest.Mock).mockImplementation((selector) => selector(mockState)); - - const { result } = renderHook(() => usePaywall()); - expect(result.current.plan).toBe(SaasPlans.Pro); - }); - - it('should return active clusters', () => { - const activeClusters = [ - { id: 1, isActive: true }, - { id: 2, isActive: true }, - ]; - - const mockState = { - api: { clusterMap: {} }, - subscription: { - license: { plan: { name: SaasPlans.Pro }, is_active: true, clusters: activeClusters }, - }, - }; - - (useAppSelector as jest.Mock).mockImplementation((selector) => selector(mockState)); - - const { result } = renderHook(() => usePaywall()); - expect(result.current.activeClusters).toEqual(activeClusters); - }); - - it('should return false if the feature is not available', () => { - const mockState = { - api: { clusterMap: {} }, - subscription: { - license: { plan: { name: SaasPlans.Pro, features: [] }, is_active: true, clusters: [] }, - }, - }; - - (useAppSelector as jest.Mock).mockImplementation((selector) => selector(mockState)); - - const { result } = renderHook(() => usePaywall()); - expect(result.current.canUseFeature('some_feature')).toBe(false); - }); - - describe('pro plan', () => { - it('should return true if the feature is available', () => { - const featureCode = 'some_feature'; - const mockState = { - api: { clusterMap: {} }, - subscription: { - license: { - plan: { name: SaasPlans.Pro, features: [{ code: featureCode }] }, - is_active: true, - clusters: [], - }, - }, - }; - - (useAppSelector as jest.Mock).mockImplementation((selector) => selector(mockState)); - - const { result } = renderHook(() => usePaywall()); - expect(result.current.canUseFeature(featureCode)).toBe(true); - }); - - it('should allow feature if cluster limit is not exceeded', () => { - const activeClusters = [ - { id: 1, isActive: true }, - { id: 2, isActive: true }, - ]; - const featureCode = SaasFeatures.WorkloadClustersLimit; - const mockState = { - api: { clusterMap: {} }, - subscription: { - license: { - plan: { - name: SaasPlans.Pro, - features: [{ code: featureCode, data: { limit: 3 } }], - }, - is_active: true, - clusters: activeClusters, - }, - }, - }; - - (useAppSelector as jest.Mock).mockImplementation((selector) => selector(mockState)); - - const { result } = renderHook(() => usePaywall()); - expect(result.current.canUseFeature(featureCode)).toBe(true); - }); - - it('should not allow feature if cluster limit has exceeded', () => { - const activeClusters = [ - { id: 1, isActive: true }, - { id: 2, isActive: true }, - { id: 2, isActive: true }, - { id: 2, isActive: true }, - ]; - const featureCode = SaasFeatures.WorkloadClustersLimit; - const mockState = { - api: { clusterMap: {} }, - subscription: { - license: { - plan: { - name: SaasPlans.Pro, - features: [{ code: featureCode, data: { limit: 3 } }], - }, - is_active: true, - clusters: activeClusters, - }, - }, - }; - - (useAppSelector as jest.Mock).mockImplementation((selector) => selector(mockState)); - - const { result } = renderHook(() => usePaywall()); - expect(result.current.canUseFeature(featureCode)).toBe(false); - }); - }); - - describe('community plan', () => { - it('should enforce cluster limit for Community plan without license key', () => { - const clusterMap = { - cluster1: { id: 1 }, - cluster2: { id: 2 }, - cluster3: { id: 3 }, - cluster4: { id: 4 }, - }; - - const mockState = { - api: { clusterMap }, - subscription: {}, - }; - - (useAppSelector as jest.Mock).mockImplementation((selector) => selector(mockState)); - - const { result } = renderHook(() => usePaywall()); - expect(result.current.canUseFeature(SaasFeatures.WorkloadClustersLimit)).toBe(false); - }); - - it('should allow use feature for Community plan without license key when the clusters are less than 3', () => { - const clusterMap = { - cluster1: { id: 1 }, - cluster2: { id: 2 }, - draft: { id: 3 }, - }; - - const mockState = { - api: { clusterMap }, - subscription: {}, - }; - - (useAppSelector as jest.Mock).mockImplementation((selector) => selector(mockState)); - - const { result } = renderHook(() => usePaywall()); - expect(result.current.canUseFeature(SaasFeatures.WorkloadClustersLimit)).toBe(true); - }); - }); -}); diff --git a/hooks/usePaywall.ts b/hooks/usePaywall.ts deleted file mode 100644 index bac2b009..00000000 --- a/hooks/usePaywall.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { useMemo } from 'react'; - -import { useAppSelector } from '@/redux/store'; -import { SaasFeatures, SaasPlans } from '@/types/subscription'; - -export const CLUSTERS_LIMIT_FALLBACK: { [key: string]: number } = { - [SaasPlans.Community]: 3, - [SaasPlans.Pro]: 10, - [SaasPlans.Enterprise]: Infinity, -}; - -export default function usePaywall() { - const { clusterMap, license, plan } = useAppSelector(({ api, subscription }) => ({ - clusterMap: api.clusterMap, - license: subscription.license, - plan: subscription.license?.plan?.name, - })); - - const canUseFeature = (featureCode: string): boolean => { - if (license?.plan && license?.is_active) { - const feature = license.plan.features.find(({ code }) => code === featureCode); - - if (featureCode === SaasFeatures.WorkloadClustersLimit) { - const clusterLimit = feature?.data.limit || CLUSTERS_LIMIT_FALLBACK[plan as string]; - - return !!activeClusters && clusterLimit > activeClusters.length; - } - - return !!feature; - } - - if (!license?.licenseKey) { - // Gets and checks number of clusters to allow workload creation - return ( - Object.keys(clusterMap).filter((clusterKey) => { - const { status } = clusterMap[clusterKey]; - return clusterKey != 'draft' && status != 'deleted'; - }).length < CLUSTERS_LIMIT_FALLBACK[SaasPlans.Community] - ); - } - - return false; - }; - - const activeClusters = useMemo( - () => license?.clusters?.filter(({ isActive }) => isActive), - [license?.clusters], - ); - - return { - canUseFeature, - activeClusters, - plan, - }; -} diff --git a/hooks/usePhysicalClustersPermission.ts b/hooks/usePhysicalClustersPermission.ts deleted file mode 100644 index c5538cfe..00000000 --- a/hooks/usePhysicalClustersPermission.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { useCallback, useMemo } from 'react'; - -import { useAppSelector } from '@/redux/store'; -import { InstallationType } from '@/types/redux'; -import { selectFeatureFlags } from '@/redux/selectors/featureFlags.selector'; -import { FeatureFlag } from '@/types/config'; - -export function usePhysicalClustersPermissions(installationType?: InstallationType) { - const { flags } = useAppSelector(selectFeatureFlags()); - const canProvisionAwsPhysicalClusters = flags[FeatureFlag.PROVISION_AWS_PYHS_CLUSTERS]; - const canProvisionDOPhysicalClusters = flags[FeatureFlag.PROVISION_DO_PYHS_CLUSTERS]; - const canProvisionGCPPhysicalClusters = flags[FeatureFlag.PROVISION_GCP_PYHS_CLUSTERS]; - const canProvisionVultrPhysicalClusters = flags[FeatureFlag.PROVISION_VULTR_PYHS_CLUSTERS]; - - // check if user has permission to provision physical clusters based on cloud provider, - // otherwise default to true if no feature flag check - const physicalClustersPermission = useMemo( - (): Record => ({ - [InstallationType.AWS]: canProvisionAwsPhysicalClusters, - [InstallationType.DIGITAL_OCEAN]: canProvisionDOPhysicalClusters, - [InstallationType.GOOGLE]: canProvisionGCPPhysicalClusters, - [InstallationType.VULTR]: canProvisionVultrPhysicalClusters, - [InstallationType.CIVO]: true, - [InstallationType.LOCAL]: true, - [InstallationType.AKAMAI]: true, - }), - [ - canProvisionAwsPhysicalClusters, - canProvisionDOPhysicalClusters, - canProvisionGCPPhysicalClusters, - canProvisionVultrPhysicalClusters, - ], - ); - - const getHasPermission = useCallback(() => { - if (!installationType) { - return false; - } - - return installationType ? physicalClustersPermission[installationType] : false; - }, [physicalClustersPermission, installationType]); - - // have to pass a function that fetches permissions since i am unable to pass a simple boolean from - // physical clusters permissions indexed by installation type - // encountered error - TypeError: getSnapshot is not a function - return { hasPermissions: getHasPermission() }; -} diff --git a/hooks/useQueue/queue.provider.tsx b/hooks/useQueue/queue.provider.tsx index 9a9145bd..f31da909 100644 --- a/hooks/useQueue/queue.provider.tsx +++ b/hooks/useQueue/queue.provider.tsx @@ -18,7 +18,7 @@ import { getClusters } from '@/redux/thunks/api.thunk'; import { RESERVED_DRAFT_CLUSTER_NAME } from '@/constants'; const QueueProvider: FunctionComponent = ({ children }) => { - const { clusterQueue, clusterMap } = useAppSelector(({ queue, api }) => ({ ...queue, ...api })); + const { clusterQueue } = useAppSelector(({ queue }) => ({ ...queue })); const dispatch = useAppDispatch(); const queue: { [key: string]: NodeJS.Timer } = useMemo(() => ({}), []); @@ -26,9 +26,7 @@ const QueueProvider: FunctionComponent = ({ children }) => { const getClusterInterval = useCallback( ({ clusterName }: ClusterQueue) => { return setInterval(async () => { - const { clusterCache } = await dispatch(getClusters()).unwrap(); - - const { status } = clusterCache[clusterName]; + const { status } = await dispatch(getClusters()).unwrap(); dispatch( setClusterQueue({ @@ -83,23 +81,6 @@ const QueueProvider: FunctionComponent = ({ children }) => { }; useEffect(() => { - // Look inside of clusterMap as well for the workload clusters that have been provisioned - // during the during the creation of the management cluster. - // omit draft cluster that is a part of the clusterMap during provisioning of a - // new workload cluster - Object.values(clusterMap).forEach(({ clusterId, clusterName, status }) => { - if ( - clusterId !== RESERVED_DRAFT_CLUSTER_NAME && - !queue[clusterName] && - [ClusterStatus.DELETING, ClusterStatus.PROVISIONING].includes(status) - ) { - queue[clusterName] = getClusterInterval({ clusterName, status }); - } - if (status === ClusterStatus.DELETED) { - dispatch(removeClusterFromQueue(clusterName)); - } - }); - Object.values(clusterQueue).forEach(({ clusterName, status }) => { if ( clusterName !== RESERVED_DRAFT_CLUSTER_NAME && @@ -112,7 +93,7 @@ const QueueProvider: FunctionComponent = ({ children }) => { dispatch(removeClusterFromQueue(clusterName)); } }); - }, [clusterQueue, getClusterInterval, queue, dispatch, clusterMap]); + }, [clusterQueue, getClusterInterval, queue, dispatch]); return ( (null); - - useEffect(() => { - const newSocket = new WebSocket(url); - setSocket(newSocket); - return () => { - newSocket.close(); - }; - }, [url]); - - return socket; -} diff --git a/jest-preset.json b/jest-preset.json deleted file mode 100644 index e69de29b..00000000 diff --git a/pages/api/auth/[...nextauth].ts b/pages/api/auth/[...nextauth].ts deleted file mode 100644 index 037c2573..00000000 --- a/pages/api/auth/[...nextauth].ts +++ /dev/null @@ -1,57 +0,0 @@ -import NextAuth, { NextAuthOptions } from 'next-auth'; -import { OAuthConfig } from 'next-auth/providers'; - -import { Route } from '@/constants'; - -interface User { - id: string; - name: string; - email: string; - groups: Array; -} - -const nestJSOAuth: OAuthConfig = { - id: 'vault', - name: 'vault', - type: 'oauth', - version: '2.0', - jwks_endpoint: `https://vault.${process.env.DOMAIN}/v1/identity/oidc/provider/kubefirst/.well-known/keys`, - authorization: { - url: `https://vault.${process.env.DOMAIN}/ui/vault/identity/oidc/provider/kubefirst/authorize`, - params: { - scope: 'openid email profile user groups id', - }, - }, - token: `https://vault.${process.env.DOMAIN}/v1/identity/oidc/provider/kubefirst/token`, - issuer: `https://vault.${process.env.DOMAIN}/v1/identity/oidc/provider/kubefirst`, - idToken: true, - async profile(profile: User): Promise { - return { ...profile, id: profile.email }; - }, - clientId: process.env.CLIENT_ID, - clientSecret: process.env.SECRET_ID, -}; - -export const authOptions: NextAuthOptions = { - providers: [nestJSOAuth], - session: { - strategy: 'jwt', - maxAge: 3600, - }, - callbacks: { - async jwt({ token, account, profile }) { - if (account) { - token.groups = profile?.groups; - } - return token; - }, - async session({ user, session, token }) { - return { ...user, ...session, groups: token.groups }; - }, - }, - pages: { - signIn: Route.SIGN_IN, - }, -}; - -export default NextAuth(authOptions); diff --git a/pages/api/proxy.ts b/pages/api/proxy.ts index 754cb12a..4910ff0a 100644 --- a/pages/api/proxy.ts +++ b/pages/api/proxy.ts @@ -1,18 +1,16 @@ import axios from 'axios'; import type { NextApiRequest, NextApiResponse } from 'next'; -const API_TARGET_ID = 'api'; - export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const { API_URL = '', ENTERPRISE_API_URL = '', K1_ACCESS_TOKEN = '' } = process.env; + const { API_URL = '', K1_ACCESS_TOKEN = '' } = process.env; const { body, url } = req.body; - const { url: queryUrl, target = API_TARGET_ID } = req.query; + const { url: queryUrl } = req.query; if (!API_URL) { return res.status(200).json('API_URL not provided'); } - const apiBaseUrl = target === API_TARGET_ID ? API_URL : ENTERPRISE_API_URL; + const apiBaseUrl = API_URL; // eslint-disable-next-line no-console console.log('BASE URL:', apiBaseUrl); diff --git a/redux/selectors/applications.selector.ts b/redux/selectors/applications.selector.ts deleted file mode 100644 index a3353151..00000000 --- a/redux/selectors/applications.selector.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; - -import { RootState } from '@/redux/store'; - -const applicationsSelector = (state: RootState) => state.applications; - -export const selectApplications = () => - createSelector(applicationsSelector, (applicationsState) => applicationsState); diff --git a/redux/selectors/environments.selector.ts b/redux/selectors/environments.selector.ts deleted file mode 100644 index 514d887b..00000000 --- a/redux/selectors/environments.selector.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; - -import { RootState } from '@/redux/store'; - -const environmentsSelector = (state: RootState) => state.environments; - -export const selectEnvironmentsState = () => - createSelector(environmentsSelector, (environmentsState) => environmentsState); diff --git a/redux/selectors/subscription.selector.ts b/redux/selectors/subscription.selector.ts deleted file mode 100644 index 9669e28e..00000000 --- a/redux/selectors/subscription.selector.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; - -import { RootState } from '@/redux/store'; -import { SaasPlans } from '@/types/subscription'; - -const subscriptionSelector = (state: RootState) => state.subscription; - -export const selectHasLicenseKey = () => - createSelector(subscriptionSelector, ({ license }) => !!license?.licenseKey); - -export const selectSubscriptionPlan = () => - createSelector(subscriptionSelector, ({ license }) => license?.plan?.name); - -export const selectIsLicenseActive = () => - createSelector(subscriptionSelector, ({ license }) => !!license?.is_active); - -export const selectPendingInvoice = () => - createSelector( - subscriptionSelector, - ({ license }) => license?.invoices && license?.invoices.find(({ status }) => status === 'open'), - ); - -export const selectUpgradeLicenseDefinition = () => - createSelector(subscriptionSelector, ({ license }) => { - if (!license?.licenseKey) { - return { - text: 'You’ve reached the workload clusters limit.', - description: 'Upgrade to a Pro plan to provision the number of clusters you need.', - ctaText: 'Upgrade to a Pro plan', - }; - } - - if (license?.plan?.name === SaasPlans.Pro) { - return { - text: 'You’ve reached the workload clusters limit.', - description: 'Upgrade to an Enterprise plan to provision the number of clusters you need.', - ctaText: 'Contact us to upgrade', - }; - } - }); - -export const selectRequestByType = (requestType: string) => - createSelector( - subscriptionSelector, - ({ license }) => - license?.requests && license?.requests.find(({ type }) => type === requestType), - ); diff --git a/redux/slices/api.slice.ts b/redux/slices/api.slice.ts index 9fff30e7..78f07992 100644 --- a/redux/slices/api.slice.ts +++ b/redux/slices/api.slice.ts @@ -3,8 +3,6 @@ import { sortBy } from 'lodash'; import { createCluster, - createWorkloadCluster, - deleteCluster, getCloudDomains, getCloudRegions, getClusters, @@ -15,14 +13,8 @@ import { ManagementCluster, ClusterCreationStep, ClusterStatus, - NewWorkloadClusterConfig, - WorkloadCluster, Cluster, - DraftCluster, } from '../../types/provision'; -import { ClusterCache } from '../../types/redux'; - -import { RESERVED_DRAFT_CLUSTER_NAME } from '@/constants'; export interface ApiState { loading: boolean; @@ -40,8 +32,6 @@ export interface ApiState { instanceSizes: string[]; isAuthenticationValid?: boolean; clusterCreationStep: ClusterCreationStep; - clusterConfig?: NewWorkloadClusterConfig; - clusterMap: ClusterCache; } export const initialState: ApiState = { @@ -58,7 +48,6 @@ export const initialState: ApiState = { instanceSizes: [], isAuthenticationValid: undefined, clusterCreationStep: ClusterCreationStep.CONFIG, - clusterMap: {}, }; const apiSlice = createSlice({ @@ -77,9 +66,6 @@ const apiSlice = createSlice({ state.isError = payload?.status === ClusterStatus.ERROR; state.isProvisioned = payload?.status === ClusterStatus.PROVISIONED; }, - setClusterMap: (state, { payload }: PayloadAction) => { - state.clusterMap = payload; - }, setCompletedSteps: (state, action) => { state.completedSteps = action.payload; }, @@ -102,25 +88,6 @@ const apiSlice = createSlice({ setClusterCreationStep: (state, { payload }: PayloadAction) => { state.clusterCreationStep = payload; }, - setClusterConfig: (state, { payload }: PayloadAction) => { - state.clusterConfig = payload; - }, - createDraftCluster: (state, { payload }: PayloadAction) => { - state.clusterMap[payload.clusterId] = payload; - state.presentedClusterName = payload.clusterName; - }, - removeDraftCluster: (state) => { - delete state.clusterMap[RESERVED_DRAFT_CLUSTER_NAME]; - }, - updateDraftCluster: (state, { payload }: PayloadAction) => { - const currentDraftCluster = state.clusterMap[RESERVED_DRAFT_CLUSTER_NAME]; - if (currentDraftCluster) { - state.clusterMap[RESERVED_DRAFT_CLUSTER_NAME] = { - ...currentDraftCluster, - ...payload, - }; - } - }, clearResponseError: (state) => { state.responseError = undefined; }, @@ -138,40 +105,9 @@ const apiSlice = createSlice({ state.isError = true; state.responseError = action.error.message; }) - .addCase(createWorkloadCluster.pending, (state) => { - state.loading = true; - }) - .addCase(createWorkloadCluster.rejected, (state, action) => { - state.loading = false; - state.isError = true; - state.responseError = action.error.message; - }) - .addCase(createWorkloadCluster.fulfilled, (state, { payload }) => { - state.loading = false; - - state.presentedClusterName = payload.clusterName; - state.clusterMap[payload.clusterName] = payload; - delete state.clusterMap[RESERVED_DRAFT_CLUSTER_NAME]; - - state.clusterCreationStep = ClusterCreationStep.DETAILS; - }) - .addCase(deleteCluster.pending, (state) => { - if (state.presentedClusterName) { - state.clusterMap[state.presentedClusterName].status = ClusterStatus.DELETING; - } - }) - .addCase(deleteCluster.fulfilled, (state) => { - state.loading = false; - }) - .addCase(deleteCluster.rejected, (state, action) => { - state.loading = false; - state.isError = true; - state.responseError = action.error.message; - }) - .addCase(getClusters.fulfilled, (state, { payload: { managementCluster, clusterCache } }) => { + .addCase(getClusters.fulfilled, (state, { payload: managementCluster }) => { state.loading = false; state.managementCluster = managementCluster; - state.clusterMap = { ...state.clusterMap, ...clusterCache }; state.lastErrorCondition = managementCluster.lastErrorCondition; state.isError = managementCluster.status === ClusterStatus.ERROR; @@ -239,13 +175,8 @@ export const { clearClusterState, clearDomains, setClusterCreationStep, - setClusterConfig, - createDraftCluster, - removeDraftCluster, - updateDraftCluster, setPresentedClusterName, setManagementCluster, - setClusterMap, clearResponseError, } = apiSlice.actions; diff --git a/redux/slices/applications.slice.ts b/redux/slices/applications.slice.ts deleted file mode 100644 index 92a42b2c..00000000 --- a/redux/slices/applications.slice.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; - -import { - getClusterApplications, - getGitOpsCatalogApps, - installGitOpsApp, - uninstallGitOpsApp, - validateGitOpsApplication, -} from '@/redux/thunks/applications.thunk'; -import { GitOpsCatalogApp, ClusterApplication, Target, AppCategory } from '@/types/applications'; -import { ManagementCluster, WorkloadCluster } from '@/types/provision'; - -export interface ApplicationsState { - selectedCluster?: ManagementCluster | WorkloadCluster; - selectedCategories: AppCategory[]; - selectedApplicationName?: string; - selectedCatalogApp?: GitOpsCatalogApp; - canDeleteSelectedApp: boolean; - clusterApplications: Array; - installedClusterAppNames: ClusterApplication['name'][]; - selectedClusterApplication?: ClusterApplication; - isLoading: boolean; - isValidating: boolean; - gitOpsCatalogApps: Array; - appsQueue: Array; - filter: { target?: Target; cluster?: string }; -} - -export const initialState: ApplicationsState = { - selectedCluster: undefined, - selectedClusterApplication: undefined, - selectedCategories: [], - clusterApplications: [], - installedClusterAppNames: [], - isLoading: false, - isValidating: false, - gitOpsCatalogApps: [], - canDeleteSelectedApp: true, - appsQueue: [], - filter: { - target: Target.CLUSTER, - cluster: '', - }, -}; - -const applicationsSlice = createSlice({ - name: 'applications', - initialState, - reducers: { - setSelectedCluster: ( - state, - { payload }: PayloadAction, - ) => { - state.selectedCluster = payload; - }, - setFilterState: (state, { payload }: PayloadAction) => { - const targetChanged = payload.target !== state.filter.target; - if (targetChanged) { - state.clusterApplications = []; - } - state.filter = { - ...payload, - cluster: targetChanged ? undefined : payload.cluster, - }; - }, - addAppToQueue: (state, { payload }: PayloadAction) => { - state.appsQueue.push(payload); - }, - removeAppFromQueue: (state, { payload }: PayloadAction) => { - state.appsQueue = state.appsQueue.filter((name) => name !== payload); - }, - resetClusterApplications: (state) => { - state.clusterApplications = []; - }, - addCategory: (state, { payload }: PayloadAction) => { - state.selectedCategories = [...state.selectedCategories, payload]; - }, - removeCategory: (state, { payload }: PayloadAction) => { - state.selectedCategories = state.selectedCategories.filter( - (selectedCategory) => selectedCategory !== payload, - ); - }, - setSelectedCatalogApp: ( - state, - { payload }: PayloadAction, - ) => { - state.selectedCatalogApp = payload; - }, - setSelectedClusterApplication: ( - state, - { payload }: PayloadAction, - ) => { - state.selectedClusterApplication = payload; - }, - }, - extraReducers: (builder) => { - builder - .addCase(getClusterApplications.pending, (state) => { - state.clusterApplications = []; - state.installedClusterAppNames = []; - }) - .addCase(getClusterApplications.fulfilled, (state, { payload }) => { - if (payload) { - state.clusterApplications = payload; - - state.installedClusterAppNames = payload.map((app) => app.name); - } else { - state.installedClusterAppNames = []; - state.clusterApplications = []; - } - }) - .addCase(getClusterApplications.rejected, (state) => { - state.clusterApplications = []; - state.installedClusterAppNames = []; - }) - .addCase(installGitOpsApp.fulfilled, (state, { payload }) => { - state.appsQueue = state.appsQueue.filter((name) => name !== payload.name); - - const { name, description, image_url } = payload; - state.clusterApplications.push({ - default: false, - description: description as string, - name, - image: image_url, - links: [], - }); - state.installedClusterAppNames.push(name); - }) - .addCase(installGitOpsApp.rejected, (state) => { - const queue = Object.assign(state.appsQueue, []); - queue.pop(); - state.appsQueue = queue; - }) - .addCase( - getGitOpsCatalogApps.fulfilled, - (state, { payload }: PayloadAction>) => { - state.gitOpsCatalogApps = payload; - }, - ) - .addCase(getGitOpsCatalogApps.rejected, (state) => { - state.gitOpsCatalogApps = []; - }) - .addCase(uninstallGitOpsApp.pending, (state) => { - state.isLoading = true; - }) - .addCase(uninstallGitOpsApp.fulfilled, (state) => { - state.isLoading = false; - }) - .addCase(uninstallGitOpsApp.rejected, (state) => { - state.isLoading = false; - }) - .addCase(validateGitOpsApplication.pending, (state) => { - state.isValidating = true; - }) - .addCase(validateGitOpsApplication.fulfilled, (state, { payload }) => { - state.canDeleteSelectedApp = !!payload.can_delete_service; - state.isValidating = false; - }) - .addCase(validateGitOpsApplication.rejected, (state) => { - state.canDeleteSelectedApp = true; - state.isValidating = false; - }); - }, -}); - -export const { - addAppToQueue, - removeAppFromQueue, - resetClusterApplications, - setFilterState, - addCategory, - removeCategory, - setSelectedCatalogApp, - setSelectedCluster, - setSelectedClusterApplication, -} = applicationsSlice.actions; - -export const applicationsReducer = applicationsSlice.reducer; diff --git a/redux/slices/config.slice.ts b/redux/slices/config.slice.ts index cccb92c4..d666aeef 100644 --- a/redux/slices/config.slice.ts +++ b/redux/slices/config.slice.ts @@ -12,7 +12,6 @@ export interface ConfigState { installMethod?: string; isLoading: boolean; clusterManagementTab: ClusterManagementTab; - saasURL?: string; } export const initialState: ConfigState = { @@ -20,7 +19,6 @@ export const initialState: ConfigState = { isTelemetryDisabled: false, isClusterZero: false, isLoading: false, - saasURL: '', clusterManagementTab: ClusterManagementTab.GRAPH_VIEW, }; @@ -29,21 +27,14 @@ const configSlice = createSlice({ initialState, reducers: { setConfigValues: (state, action: PayloadAction) => { - const { - isClusterZero, - installMethod, - disableTelemetry, - kubefirstVersion, - disableAuth, - saasURL, - } = action.payload; + const { isClusterZero, installMethod, disableTelemetry, kubefirstVersion, disableAuth } = + action.payload; state.isTelemetryDisabled = !!disableTelemetry; state.kubefirstVersion = kubefirstVersion; state.isClusterZero = isClusterZero; state.installMethod = installMethod; state.isAuthDisabled = !!disableAuth; - state.saasURL = saasURL; }, setClusterManagamentTab: (state, { payload }: PayloadAction) => { state.clusterManagementTab = payload; diff --git a/redux/slices/environments.slice.ts b/redux/slices/environments.slice.ts deleted file mode 100644 index 29ca1aeb..00000000 --- a/redux/slices/environments.slice.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { PayloadAction, createSlice } from '@reduxjs/toolkit'; - -import { ClusterEnvironment } from '../../types/provision'; -import { - createEnvironment, - deleteEnvironment, - getAllEnvironments, -} from '../thunks/environments.thunk'; -import { EnvCache } from '../../types/redux'; -import { getClusters } from '../thunks/api.thunk'; - -export type EnvMap = Record; -export interface EnvironmentsState { - loading: boolean; - environments: EnvMap; - /** - * environments cache for environments in use by cluster(s) - */ - boundEnvironments: EnvCache; - error?: string; -} - -export const initialState: EnvironmentsState = { - loading: false, - environments: {}, - boundEnvironments: {}, -}; - -const environmentsSlice = createSlice({ - name: 'environments', - initialState, - reducers: { - setEnvironments: (state, { payload }: PayloadAction) => { - state.environments = payload; - }, - setBoundEnvironments: (state, { payload }: PayloadAction) => { - state.boundEnvironments = payload; - }, - setEnvironmentError: (state, { payload }: PayloadAction) => { - state.error = payload; - }, - clearEnvironmentError: (state) => { - state.error = undefined; - }, - }, - extraReducers: (builder) => { - builder - .addCase(getAllEnvironments.pending, (state) => { - state.loading = true; - }) - .addCase(getAllEnvironments.rejected, (state, action) => { - state.loading = false; - state.environments = {}; - state.error = action.error.message; - }) - .addCase(getAllEnvironments.fulfilled, (state, { payload }) => { - state.loading = false; - state.environments = payload; - }) - .addCase(createEnvironment.pending, (state) => { - state.loading = true; - }) - .addCase(createEnvironment.rejected, (state, action) => { - state.loading = false; - state.error = action.error.message; - }) - .addCase(createEnvironment.fulfilled, (state, { payload }) => { - state.loading = false; - state.environments[payload.id] = payload; - }) - .addCase(deleteEnvironment.pending, (state) => { - state.loading = true; - }) - .addCase(deleteEnvironment.rejected, (state, action) => { - state.loading = false; - state.error = action.error.message; - }) - .addCase(deleteEnvironment.fulfilled, (state, { payload: envId }) => { - state.loading = false; - delete state.environments[envId]; - }) - .addCase(getClusters.fulfilled, (state, { payload: { envCache } }) => { - state.boundEnvironments = envCache; - }); - }, -}); - -export const { setEnvironments, setBoundEnvironments, setEnvironmentError, clearEnvironmentError } = - environmentsSlice.actions; - -export const environmentsReducer = environmentsSlice.reducer; diff --git a/redux/slices/index.ts b/redux/slices/index.ts index 012e7eae..241b0246 100644 --- a/redux/slices/index.ts +++ b/redux/slices/index.ts @@ -1,14 +1,8 @@ -export { readinessReducer } from './readiness.slice'; export { gitReducer } from './git.slice'; export { configReducer } from './config.slice'; export { installationReducer } from './installation.slice'; export { apiReducer } from './api.slice'; export { featureFlagsReducer } from './featureFlags.slice'; -export { applicationsReducer } from './applications.slice'; -export { reactFlowReducer } from './reactFlow.slice'; export { queueReducer } from './queue.slice'; -export { environmentsReducer } from './environments.slice'; export { notificationsReducer } from './notifications.slice'; -export { settingsReducer } from './settings.slice'; -export { subscriptionReducer } from './subscription.slice'; export { digitalOceanReducer } from './digitalOcean.slice'; diff --git a/redux/slices/installation.slice.ts b/redux/slices/installation.slice.ts index 5b6cb8fd..d0a0d75f 100644 --- a/redux/slices/installation.slice.ts +++ b/redux/slices/installation.slice.ts @@ -53,21 +53,11 @@ const installationSlice = createSlice({ }, }, extraReducers: (builder) => { - builder.addCase( - getClusters.fulfilled, - ( - state, - { - payload: { - managementCluster: { status, lastErrorCondition }, - }, - }, - ) => { - if (status === ClusterStatus.ERROR) { - state.error = lastErrorCondition; - } - }, - ); + builder.addCase(getClusters.fulfilled, (state, { payload: { status, lastErrorCondition } }) => { + if (status === ClusterStatus.ERROR) { + state.error = lastErrorCondition; + } + }); }, }); diff --git a/redux/slices/queue.slice.ts b/redux/slices/queue.slice.ts index 058b4c4d..6ec0dc66 100644 --- a/redux/slices/queue.slice.ts +++ b/redux/slices/queue.slice.ts @@ -1,7 +1,7 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { ClusterQueue } from '../../types/provision'; -import { createCluster, createWorkloadCluster, deleteCluster } from '../thunks/api.thunk'; +import { createCluster } from '../thunks/api.thunk'; import { Cluster } from '../../types/provision'; export type Queue = { @@ -30,16 +30,9 @@ const queueSlice = createSlice({ }, }, extraReducers: (builder) => { - builder - .addCase(createCluster.fulfilled, (state, { payload: { clusterName, status } }) => { - state.clusterQueue[clusterName] = { clusterName, status }; - }) - .addCase(createWorkloadCluster.fulfilled, (state, { payload: { clusterName, status } }) => { - state.clusterQueue[clusterName] = { clusterName, status }; - }) - .addCase(deleteCluster.fulfilled, (state, { payload: { clusterName, status } }) => { - state.clusterQueue[clusterName] = { clusterName, status }; - }); + builder.addCase(createCluster.fulfilled, (state, { payload: { clusterName, status } }) => { + state.clusterQueue[clusterName] = { clusterName, status }; + }); }, }); diff --git a/redux/slices/reactFlow.slice.ts b/redux/slices/reactFlow.slice.ts deleted file mode 100644 index 5ac6aadc..00000000 --- a/redux/slices/reactFlow.slice.ts +++ /dev/null @@ -1,83 +0,0 @@ -'use client'; -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { - addEdge, - applyEdgeChanges, - applyNodeChanges, - Connection, - Edge, - EdgeChange, - NodeChange, -} from 'reactflow'; - -import { setPresentedClusterName } from './api.slice'; - -import { CustomGraphNode } from '@/components/GraphNode/GraphNode'; - -export interface ReactFlowState { - nodes: CustomGraphNode[]; - edges: Edge[]; -} - -export const initialState: ReactFlowState = { - nodes: [], - edges: [], -}; - -const reactFlowSlice = createSlice({ - name: 'react-flow', - initialState, - reducers: { - setNodes: (state, { payload }: PayloadAction) => { - state.nodes = payload; - }, - addNode: (state, { payload }: PayloadAction) => { - state.nodes = [...state.nodes, payload]; - }, - setEdges: (state, { payload }: PayloadAction) => { - state.edges = payload; - }, - addNewEdge: (state, { payload }: PayloadAction) => { - state.edges = addEdge(payload, state.edges); - }, - onNodesChange: (state, { payload }: PayloadAction) => { - state.nodes = applyNodeChanges(payload, state.nodes); - }, - onEdgesChange: (state, { payload }: PayloadAction) => { - state.edges = applyEdgeChanges(payload, state.edges); - }, - onConnect: (state, { payload }: PayloadAction) => { - state.edges = addEdge(payload, state.edges); - }, - selectNodeById: (state, { payload }: PayloadAction) => { - const selectedNode = state.nodes.find((node) => node.id === payload); - if (selectedNode) { - selectedNode.selected = true; - } - }, - unSelectNodes: (state) => { - state.nodes = state.nodes.map((node) => ({ ...node, selected: false })); - }, - }, - extraReducers: (builder) => { - builder.addCase(setPresentedClusterName, (state, { payload }) => { - if (!payload) { - state.nodes = state.nodes.map((node) => ({ ...node, selected: false })); - } - }); - }, -}); - -export const { - setNodes, - addNode, - setEdges, - addNewEdge, - onNodesChange, - onEdgesChange, - onConnect, - selectNodeById, - unSelectNodes, -} = reactFlowSlice.actions; - -export const reactFlowReducer = reactFlowSlice.reducer; diff --git a/redux/slices/readiness.slice.ts b/redux/slices/readiness.slice.ts deleted file mode 100644 index 4671d680..00000000 --- a/redux/slices/readiness.slice.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; - -import { checkSiteReadiness } from '../thunks/readiness.thunk'; - -export interface ReadinessState { - availableSites: Array; - error: string | null; - loading: boolean; -} - -export const initialState: ReadinessState = { - availableSites: [], - error: null, - loading: false, -}; - -const readinessSlice = createSlice({ - name: 'readiness', - initialState, - reducers: { - clearReadinessError: (state) => { - state.error = null; - }, - }, - extraReducers: (builder) => { - builder - .addCase(checkSiteReadiness.pending, (state) => { - state.loading = true; - }) - .addCase(checkSiteReadiness.fulfilled, (state, action) => { - state.loading = false; - state.availableSites.push(action.payload.url); - }) - .addCase(checkSiteReadiness.rejected, (state, action) => { - state.loading = false; - state.error = action.error.message ?? 'failed to check readiness'; - }); - }, -}); - -export const { clearReadinessError } = readinessSlice.actions; - -export const readinessReducer = readinessSlice.reducer; diff --git a/redux/slices/settings.slice.ts b/redux/slices/settings.slice.ts deleted file mode 100644 index 0f91e12b..00000000 --- a/redux/slices/settings.slice.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { PayloadAction, createSlice } from '@reduxjs/toolkit'; - -import { getClusterTourStatus, updateClusterTourStatus } from '../thunks/settings.thunk'; - -import { SettingsTab } from '@/constants/setttings'; - -export interface SettingsState { - isLoading: boolean; - activeTab: number; - takenConsoleTour: boolean; - isBannerOpen: boolean; -} - -export const initialState: SettingsState = { - isLoading: false, - activeTab: SettingsTab.PLANS, - takenConsoleTour: false, - isBannerOpen: false, -}; - -const settingsSlice = createSlice({ - name: 'slice', - initialState, - reducers: { - setActiveTab: (state, { payload }: PayloadAction) => { - state.activeTab = payload; - }, - setIsBannerOpen: (state, { payload }: PayloadAction) => { - state.isBannerOpen = payload; - }, - }, - extraReducers: (builder) => { - builder - .addCase(getClusterTourStatus.rejected, () => { - // eslint-disable-next-line no-console - console.error('unable to retrieve console tour secret'); - }) - .addCase(getClusterTourStatus.fulfilled, (state, { payload }) => { - const tourStatus = payload === 'true'; - state.takenConsoleTour = tourStatus; - }) - .addCase(updateClusterTourStatus.rejected, () => { - // eslint-disable-next-line no-console - console.error('unable to update cluster tour status'); - }); - }, -}); - -export const { setActiveTab, setIsBannerOpen } = settingsSlice.actions; - -export const settingsReducer = settingsSlice.reducer; diff --git a/redux/slices/subscription.slice.ts b/redux/slices/subscription.slice.ts deleted file mode 100644 index a6aa6ccc..00000000 --- a/redux/slices/subscription.slice.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; - -import { ClusterUsage, License } from '@/types/subscription'; -import { - activateLicenseKey, - getClusterUsage, - validateLicenseKey, -} from '@/redux/thunks/subscription.thunk'; - -export interface LicenseState { - license?: License; - isLoading: boolean; - clusterUsageList: Array; - error?: string; -} - -export const initialState: LicenseState = { - isLoading: false, - clusterUsageList: [], -}; - -const subscriptionSlice = createSlice({ - name: 'license', - initialState, - reducers: { - setLicense: (state, { payload }: PayloadAction) => { - state.license = payload; - }, - }, - extraReducers: (builder) => { - builder - .addCase(validateLicenseKey.fulfilled, (state, { payload }) => { - state.license = payload; - state.isLoading = false; - }) - .addCase(validateLicenseKey.pending, (state) => { - state.isLoading = true; - }) - .addCase(validateLicenseKey.rejected, (state) => { - state.isLoading = false; - }) - .addCase(activateLicenseKey.fulfilled, (state) => { - state.isLoading = false; - }) - .addCase(activateLicenseKey.pending, (state) => { - state.isLoading = true; - state.error = undefined; - }) - .addCase(activateLicenseKey.rejected, (state) => { - state.isLoading = false; - state.error = - 'Please enter a valid license key. If this error persists please reach out to the kubefirst team.'; - }) - .addCase(getClusterUsage.fulfilled, (state, { payload }) => { - state.isLoading = false; - state.clusterUsageList = payload; - }) - .addCase(getClusterUsage.pending, (state) => { - state.isLoading = true; - state.error = undefined; - }) - .addCase(getClusterUsage.rejected, (state) => { - state.isLoading = false; - }); - }, -}); - -export const { setLicense } = subscriptionSlice.actions; - -export const subscriptionReducer = subscriptionSlice.reducer; diff --git a/redux/store.ts b/redux/store.ts index b241ced9..3745a9f2 100644 --- a/redux/store.ts +++ b/redux/store.ts @@ -14,14 +14,8 @@ import { gitReducer, installationReducer, queueReducer, - reactFlowReducer, - readinessReducer, - environmentsReducer, notificationsReducer, - settingsReducer, - subscriptionReducer, digitalOceanReducer, - applicationsReducer, } from './slices'; const rootReducer = combineReducers({ @@ -29,16 +23,10 @@ const rootReducer = combineReducers({ config: configReducer, installation: installationReducer, git: gitReducer, - readiness: readinessReducer, api: apiReducer, featureFlags: featureFlagsReducer, - applications: applicationsReducer, - reactFlow: reactFlowReducer, queue: queueReducer, - environments: environmentsReducer, notifications: notificationsReducer, - settings: settingsReducer, - subscription: subscriptionReducer, digitalOcean: digitalOceanReducer, }); @@ -52,7 +40,6 @@ const config = getPersistConfig({ 'installation.values.do_auth', 'installation.values.vultr_auth', 'installation.values.google_auth', - 'applications', 'api', 'featureFlags', 'git', @@ -63,9 +50,6 @@ const config = getPersistConfig({ 'config.installMethod', 'config.isLoading', 'internalApi', - 'readiness', - 'environments', - 'subscription', ], rootReducer, }); diff --git a/redux/thunks/__tests__/api.thunk.test.ts b/redux/thunks/__tests__/api.thunk.test.ts index a9803caa..946ba9b2 100644 --- a/redux/thunks/__tests__/api.thunk.test.ts +++ b/redux/thunks/__tests__/api.thunk.test.ts @@ -5,8 +5,6 @@ import { sortBy } from 'lodash'; import { makeStore } from '../../store'; import { createCluster, - createWorkloadCluster, - deleteCluster, getCloudDomains, getCloudRegions, getClusters, @@ -15,15 +13,7 @@ import { } from '../api.thunk'; import { mapClusterFromRaw } from '../../../utils/mapClustersFromRaw'; import { mockClusterResponse } from '../../../tests/mocks/mockClusterResponse'; -import { - clearResponseError, - setClusterMap, - setManagementCluster, - setPresentedClusterName, -} from '../../slices/api.slice'; -import { ClusterCreationStep, ClusterStatus } from '../../../types/provision'; -import { ClusterCache } from '../../../types/redux'; -import { RESERVED_DRAFT_CLUSTER_NAME } from '../../../constants'; +import { clearResponseError } from '../../slices/api.slice'; describe('redux/thunks/api', () => { const reduxStore = makeStore(); @@ -55,67 +45,6 @@ describe('redux/thunks/api', () => { expect(responseError).toBe('Request failed with status code 400'); }); - test('createWorkloadCluster - unsuccessful response - missing management cluster', async () => { - mock.onPost().reply(200); // set to 200 to show that thunk is throwing internally - await reduxStore.dispatch(createWorkloadCluster()); - - const { isError, responseError, managementCluster } = reduxStore.getState().api; - - expect(isError).toBe(true); - expect(responseError).toStrictEqual('missing management cluster'); - expect(managementCluster).toBe(undefined); - }); - - test('createWorkloadCluster - unsuccessful response - missing draft cluster', async () => { - mock.onPost().reply(200); // set to 200 to show that thunk is throwing internally - - const { managementCluster } = mapClusterFromRaw(mockClusterResponse); - reduxStore.dispatch(setManagementCluster(managementCluster)); - - await reduxStore.dispatch(createWorkloadCluster()); - - const { isError, responseError, managementCluster: manCluster } = reduxStore.getState().api; - - expect(isError).toBe(true); - expect(responseError).toStrictEqual('missing draft cluster'); - expect(manCluster).toStrictEqual(managementCluster); - }); - - test('createWorkloadCluster - successful response ', async () => { - const mockCreatedClusterId = 'superDopeId'; - mock.onPost().reply(200, { cluster_id: mockCreatedClusterId }); - - const { managementCluster } = mapClusterFromRaw(mockClusterResponse); - const { workloadClusters } = managementCluster; - - const mockClusterCache: ClusterCache = { - [RESERVED_DRAFT_CLUSTER_NAME]: workloadClusters[0], - }; - - reduxStore.dispatch(setManagementCluster(managementCluster)); - reduxStore.dispatch(setClusterMap(mockClusterCache)); - - await reduxStore.dispatch(createWorkloadCluster()); - - const { - isError, - responseError, - managementCluster: manCluster, - clusterMap, - clusterCreationStep, - } = reduxStore.getState().api; - - const provisioningCluster = clusterMap[workloadClusters[0].clusterName]; - - expect(responseError).toBe(undefined); - expect(isError).toBe(false); - expect(manCluster).toStrictEqual(managementCluster); - expect(provisioningCluster).toBeDefined(); - expect(provisioningCluster.clusterId).toStrictEqual(mockCreatedClusterId); - expect(provisioningCluster.status).toStrictEqual(ClusterStatus.PROVISIONING); - expect(clusterCreationStep).toStrictEqual(ClusterCreationStep.DETAILS); - }); - test('getClusters - successful response', async () => { mock.onGet().reply(200, [mockClusterResponse]); @@ -123,12 +52,8 @@ describe('redux/thunks/api', () => { await reduxStore.dispatch(getClusters()); - const { managementCluster, clusterMap } = reduxStore.getState().api; - const { boundEnvironments } = reduxStore.getState().environments; - - expect(managementCluster).toStrictEqual(mockResult.managementCluster); - expect(clusterMap).toStrictEqual(mockResult.clusterCache); - expect(boundEnvironments).toStrictEqual(mockResult.envCache); + const { managementCluster } = reduxStore.getState().api; + expect(managementCluster).toStrictEqual(mockResult); }); test('getClusters - unsuccessful response - no clusters found', async () => { @@ -151,26 +76,6 @@ describe('redux/thunks/api', () => { expect(responseError).toBe('Request failed with status code 400'); }); - test('deleteCluster - unsuccessful response', async () => { - mock.onGet().reply(200, [mockClusterResponse]); - mock.onDelete().reply(400); - - const { - managementCluster: { workloadClusters }, - } = mapClusterFromRaw(mockClusterResponse); - - const [{ clusterName }] = workloadClusters; - - await reduxStore.dispatch(getClusters()); - reduxStore.dispatch(setPresentedClusterName(undefined)); - await reduxStore.dispatch(deleteCluster(clusterName)); - - const { isError, responseError } = reduxStore.getState().api; - - expect(isError).toBe(true); - expect(responseError).toBe('Request failed with status code 400'); - }); - test('getCloudRegions - successful response', async () => { const mockCloudRegions = ['LON1', 'FRA1', 'JAP1', 'GERM1']; mock.onPost().reply(200, { regions: mockCloudRegions }); diff --git a/redux/thunks/__tests__/license.thunk.test.ts b/redux/thunks/__tests__/license.thunk.test.ts deleted file mode 100644 index 2ca6ee6a..00000000 --- a/redux/thunks/__tests__/license.thunk.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import axios from 'axios'; -import MockAdapter from 'axios-mock-adapter'; - -import { makeStore } from '../../store'; -import { validateLicenseKey } from '../subscription.thunk'; -import { mockUserLicense } from '../../../tests/mocks/mockUserLicense'; -import { setLicense } from '../../slices/subscription.slice'; - -//ToDo: fix test -describe('redux/thunks/license', () => { - const reduxStore = makeStore(); - - beforeEach(() => { - mock.reset(); - reduxStore.dispatch(setLicense(undefined)); - }); - - const mock = new MockAdapter(axios); - - test.skip('validateLicenseKey - successful response', async () => { - mock.reset(); - mock.onGet().reply(200, mockUserLicense); - const { payload } = await reduxStore.dispatch(validateLicenseKey()); - - const { license } = reduxStore.getState().license; - - expect(payload).toStrictEqual(mockUserLicense); - expect(payload).toStrictEqual(license); - }); - - test.skip('validateLicenseKey - unsuccessful response', async () => { - mock.reset(); - mock.onGet().reply(400); - const { payload } = await reduxStore.dispatch(validateLicenseKey()); - - const { license } = reduxStore.getState().license; - - expect(payload).toBe(undefined); - expect(payload).toStrictEqual(license); - }); -}); diff --git a/redux/thunks/api.thunk.ts b/redux/thunks/api.thunk.ts index ef247c25..a3d79f85 100644 --- a/redux/thunks/api.thunk.ts +++ b/redux/thunks/api.thunk.ts @@ -1,6 +1,8 @@ import axios from 'axios'; import { createAsyncThunk } from '@reduxjs/toolkit'; +import { clearError, setError, setInstallationStep } from '../slices/installation.slice'; + import { AppDispatch, RootState } from '@/redux/store'; import { ClusterResponse, @@ -8,18 +10,15 @@ import { ImageRepository, ClusterType, ClusterStatus, - ClusterEnvironment, - WorkloadCluster, ClusterQueue, - DraftCluster, + ManagementCluster, } from '@/types/provision'; import { createQueryString } from '@/utils/url/formatDomain'; import { InstallValues, InstallationType } from '@/types/redux'; import { TelemetryClickEvent } from '@/types/telemetry'; import { mapClusterFromRaw } from '@/utils/mapClustersFromRaw'; import { GitProtocol } from '@/types'; -import { RESERVED_DRAFT_CLUSTER_NAME } from '@/constants'; -import { getCloudProviderAuth } from '@/utils/getCloudProviderAuth'; +import { AUTHENTICATION_ERROR_MSG } from '@/constants'; export const createCluster = createAsyncThunk< ClusterQueue, @@ -88,60 +87,8 @@ export const createCluster = createAsyncThunk< return { clusterName: values?.clusterName as string, status: ClusterStatus.PROVISIONING }; }); -export const createWorkloadCluster = createAsyncThunk< - WorkloadCluster, - void, - { - dispatch: AppDispatch; - state: RootState; - } ->('api/cluster/createWorkloadCluster', async (_, { getState }) => { - const { managementCluster, clusterMap } = getState().api; - - const draftCluster = clusterMap[RESERVED_DRAFT_CLUSTER_NAME]; - - if (!managementCluster) { - throw new Error('missing management cluster'); - } - - if (!draftCluster) { - throw new Error('missing draft cluster'); - } - - const clusterEnvironment = !draftCluster.environment - ? undefined - : { - name: draftCluster.environment?.name, - color: draftCluster.environment?.color, - description: draftCluster.environment?.description, - }; - - const { cluster_id } = ( - await axios.post<{ cluster_id: string }>(`/api/proxy?target=ee`, { - url: `/cluster/${managementCluster?.clusterId}`, - body: { - cluster_name: draftCluster.clusterName, - cloud_region: draftCluster.cloudRegion, - node_type: draftCluster.instanceSize, - node_count: draftCluster.nodeCount, - environment: clusterEnvironment, - cluster_type: draftCluster.type, - }, - }) - ).data; - - const provisioningWorkloadCluster: WorkloadCluster = { - ...draftCluster, - clusterId: cluster_id, - status: ClusterStatus.PROVISIONING, - environment: draftCluster.environment as ClusterEnvironment, - }; - - return provisioningWorkloadCluster; -}); - export const getClusters = createAsyncThunk< - ReturnType, + ManagementCluster, void, { dispatch: AppDispatch; @@ -160,50 +107,33 @@ export const getClusters = createAsyncThunk< return mapClusterFromRaw(res.data[0]); }); -export const deleteCluster = createAsyncThunk< - ClusterQueue, - Cluster['clusterName'], - { - dispatch: AppDispatch; - state: RootState; - } ->('api/cluster/delete', async (clusterName, { getState }) => { - const { - api: { managementCluster, clusterMap }, - } = getState(); - - const { clusterId } = clusterMap[clusterName]; - - // returned cluster_id is unused at this time. - if (managementCluster) { - await axios.delete<{ cluster_id: string }>( - `/api/proxy?${createQueryString( - 'url', - `/cluster/${managementCluster.clusterId}/${clusterId}`, - )}&target=ee`, - ); - } - - return { clusterName, status: ClusterStatus.DELETING }; -}); - export const getCloudRegions = createAsyncThunk< Array, - { values: InstallValues | Cluster; installType?: InstallationType }, + { values: InstallValues | Cluster; installType?: InstallationType; validate: boolean }, { dispatch: AppDispatch; state: RootState; } ->('api/getCloudRegions', async ({ values, installType }) => { - const { regions } = ( - await axios.post<{ regions: Array }>('/api/proxy', { - url: `/region/${installType || (values as Cluster).cloudProvider}`, - body: - installType === InstallationType.AWS ? { ...values, cloud_region: 'us-east-1' } : values, - }) - ).data; - - return regions; +>('api/getCloudRegions', async ({ values, installType, validate }, { getState, dispatch }) => { + try { + const { installation } = getState(); + const { regions } = ( + await axios.post<{ regions: Array }>('/api/proxy', { + url: `/region/${installType || (values as Cluster).cloudProvider}`, + body: + installType === InstallationType.AWS ? { ...values, cloud_region: 'us-east-1' } : values, + }) + ).data; + + if (validate) { + dispatch(setInstallationStep(installation.installationStep + 1)); + dispatch(clearError()); + } + return regions; + } catch (error) { + dispatch(setError({ error: AUTHENTICATION_ERROR_MSG })); + throw new Error('Failed to fetch cloud regions'); + } }); export const getCloudDomains = createAsyncThunk< @@ -309,35 +239,3 @@ export const sendTelemetryEvent = createAsyncThunk< body, }); }); - -export const downloadKubeconfig = createAsyncThunk< - string, - { presentedCluster: Cluster | DraftCluster }, - { - dispatch: AppDispatch; - state: RootState; - } ->('api/downloadKubeconfig', async ({ presentedCluster }, { getState }) => { - const { managementCluster } = getState().api; - - const { clusterName, cloudProvider, cloudRegion, type } = presentedCluster; - if (managementCluster) { - const { key, value } = getCloudProviderAuth(managementCluster); - const { - data: { config }, - } = await axios.post<{ config: string }>(`/api/proxy`, { - url: `/kubeconfig/${cloudProvider}`, - body: { - cluster_name: clusterName, - man_clust_name: managementCluster.clusterName, - vcluster: type !== ClusterType.MANAGEMENT, - cloud_region: cloudRegion, - [`${key}_auth`]: value, - }, - }); - - return `data:text/yaml;chatset=utf-8,${encodeURIComponent(config)}`; - } else { - throw new Error('no management cluster found'); - } -}); diff --git a/redux/thunks/applications.thunk.ts b/redux/thunks/applications.thunk.ts deleted file mode 100644 index 56e84fb4..00000000 --- a/redux/thunks/applications.thunk.ts +++ /dev/null @@ -1,210 +0,0 @@ -import axios from 'axios'; -import { createAsyncThunk } from '@reduxjs/toolkit'; -import { FieldValues } from 'react-hook-form'; - -import { AppDispatch, RootState } from '../store'; -import { createQueryString } from '../../utils/url/formatDomain'; -import { ClusterRequestProps } from '../../types/provision'; -import { - GitOpsCatalogApp, - ClusterApplication, - Target, - ValidateGitOpsCatalog, -} from '../../types/applications'; -import { addAppToQueue, removeAppFromQueue } from '../slices/applications.slice'; -import { transformObjectToStringKey } from '../../utils/transformObjectToStringKey'; -import { createNotification } from '../slices/notifications.slice'; - -export const installGitOpsApp = createAsyncThunk< - GitOpsCatalogApp, - { values: FieldValues; user: string }, - { - dispatch: AppDispatch; - state: RootState; - } ->('applications/installGitOpsApp', async ({ values, user }, { dispatch, getState }) => { - const { - applications: { selectedCatalogApp, filter }, - api: { managementCluster, clusterMap }, - } = getState(); - - if (!selectedCatalogApp) { - throw new Error('missing selected catalog app'); - } - - const formValues = transformObjectToStringKey(values); - - const keys = Object.keys(formValues); - - const getMapValues = (values: Array<{ name: string; label: string }> | undefined) => - values && - values - .filter(({ name }) => keys?.includes(name)) - .map(({ name: key }) => ({ - name: key, - value: formValues && formValues[key], - })); - - const secret_keys = getMapValues(selectedCatalogApp.secret_keys); - const config_keys = getMapValues(selectedCatalogApp.config_keys); - - const cluster = clusterMap[filter.cluster as string]; - - const params = { - config_keys, - secret_keys, - user, - is_template: filter.target === Target.TEMPLATE, - workload_cluster_name: filter.cluster, - environment: cluster?.environment?.name, - }; - - // Removing workload_cluster_name for management cluster installations - if (managementCluster?.clusterName === filter.cluster) { - delete params.workload_cluster_name; - } - - dispatch(addAppToQueue(selectedCatalogApp.name)); - - const res = await axios.post('/api/proxy', { - // same for delete gitops app - url: `/services/${managementCluster?.clusterName}/${selectedCatalogApp.name}`, - body: params, - }); - - if ('error' in res) { - dispatch(removeAppFromQueue(selectedCatalogApp?.name)); - throw res.error; - } - - dispatch( - createNotification({ - message: `${selectedCatalogApp.display_name} successfully added to provisioned services in cluster ${filter.cluster}!`, - type: 'success', - snackBarOrigin: { vertical: 'bottom', horizontal: 'right' }, - }), - ); - - dispatch(getClusterApplications({ clusterName: filter.cluster })); - - return selectedCatalogApp; -}); - -export const getClusterApplications = createAsyncThunk< - ClusterApplication[], - ClusterRequestProps, - { - dispatch: AppDispatch; - state: RootState; - } ->('applications/getClusterApplications', async ({ clusterName }) => { - return ( - await axios.get<{ services: ClusterApplication[] }>( - `/api/proxy?${createQueryString('url', `/services/${clusterName}`)}`, - ) - ).data.services; -}); - -export const getGitOpsCatalogApps = createAsyncThunk< - GitOpsCatalogApp[], - string, - { - dispatch: AppDispatch; - state: RootState; - } ->('applications/getGitOpsCatalogApps', async (cloudProvider, { getState }) => { - const { - api: { managementCluster }, - } = getState(); - - return ( - await axios.get<{ apps: GitOpsCatalogApp[] }>( - `/api/proxy?${createQueryString( - 'url', - `/gitops-catalog/${managementCluster?.clusterName}/${cloudProvider}/apps`, - )}`, - ) - ).data.apps; -}); - -export const validateGitOpsApplication = createAsyncThunk< - ValidateGitOpsCatalog, - { application: ClusterApplication }, - { - dispatch: AppDispatch; - state: RootState; - } ->('applications/validateGitOpsApplication', async ({ application }, { getState }) => { - const { - applications: { filter }, - api: { managementCluster }, - } = getState(); - - if (!application) { - throw new Error('missing selected catalog app'); - } - - const params = { - is_template: filter.target === Target.TEMPLATE, - workload_cluster_name: filter.cluster, - }; - - // Removing workload_cluster_name for management cluster installations - if (managementCluster?.clusterName === filter.cluster) { - delete params.workload_cluster_name; - } - - return ( - await axios.post('/api/proxy', { - url: `/services/${managementCluster?.clusterName}/${application.name}/validate`, - body: params, - }) - ).data; -}); - -export const uninstallGitOpsApp = createAsyncThunk< - void, - { application: ClusterApplication; user: string }, - { - dispatch: AppDispatch; - state: RootState; - } ->('applications/uninstallGitOpsApp', async ({ application, user }, { dispatch, getState }) => { - const { - applications: { filter, canDeleteSelectedApp, selectedClusterApplication }, - api: { managementCluster }, - } = getState(); - - if (!application) { - throw new Error('missing selected catalog app'); - } - - const params = { - user, - is_template: filter.target === Target.TEMPLATE, - workload_cluster_name: filter.cluster, - skip_files: !canDeleteSelectedApp, - }; - - // Removing workload_cluster_name for management cluster installations - if (managementCluster?.clusterName === filter.cluster) { - delete params.workload_cluster_name; - } - - await axios.delete('/api/proxy', { - data: { - url: `/services/${managementCluster?.clusterName}/${application.name}`, - body: params, - }, - }); - - dispatch(removeAppFromQueue(selectedClusterApplication?.name as string)); - dispatch(getClusterApplications({ clusterName: filter.cluster })); - dispatch( - createNotification({ - message: `${application.name} successfully uninstalled from cluster ${filter.cluster}!`, - type: 'success', - snackBarOrigin: { vertical: 'bottom', horizontal: 'right' }, - }), - ); -}); diff --git a/redux/thunks/environments.thunk.ts b/redux/thunks/environments.thunk.ts deleted file mode 100644 index 59066d7b..00000000 --- a/redux/thunks/environments.thunk.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { createAsyncThunk } from '@reduxjs/toolkit'; -import axios from 'axios'; - -import { ClusterEnvironment, EnvironmentResponse } from '../../types/provision'; -import { createQueryString } from '../../utils/url/formatDomain'; -import { mapEnvironmentFromRaw } from '../../utils/mapEnvironmentFromRaw'; -import { createNotification } from '../../redux/slices/notifications.slice'; -import { EnvMap } from '../../redux/slices/environments.slice'; -import { createEnvMap } from '../../utils/createEnvMap'; - -export const getAllEnvironments = createAsyncThunk( - 'environments/getAllEnvironments', - async () => { - try { - const { data } = await axios.get( - `/api/proxy?${createQueryString('url', `/environment`)}`, - ); - - return createEnvMap(data ?? []); - } catch (error) { - if (axios.isAxiosError(error)) { - throw error.response?.data.error; - } else { - throw error; - } - } - }, -); - -export const createEnvironment = createAsyncThunk( - 'environments/createEnvironment', - async (environment) => { - try { - const { data } = await axios.post('/api/proxy', { - url: '/environment', - body: environment, - }); - return mapEnvironmentFromRaw(data); - } catch (error) { - if (axios.isAxiosError(error)) { - throw error.response?.data.error; - } else { - throw error; - } - } - }, -); - -export const deleteEnvironment = createAsyncThunk( - 'environments/deleteEnvironment', - async (environment, { dispatch }) => { - try { - await axios.delete( - `/api/proxy?${createQueryString('url', `/environment/${environment.id}`)}`, - ); - - dispatch( - createNotification({ - message: `${environment.name} environment successfully deleted.`, - type: 'success', - snackBarOrigin: { - vertical: 'bottom', - horizontal: 'right', - }, - }), - ); - - return environment.id; - } catch (error) { - if (axios.isAxiosError(error)) { - throw error.response?.data.error; - } else { - throw error; - } - } - }, -); diff --git a/redux/thunks/readiness.thunk.ts b/redux/thunks/readiness.thunk.ts deleted file mode 100644 index 39b5db18..00000000 --- a/redux/thunks/readiness.thunk.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createAsyncThunk } from '@reduxjs/toolkit'; - -import { endpoints } from '../api/'; -import { AppDispatch, RootState } from '../store'; -import { ReadinessData } from '../../pages/api/readiness'; - -export const checkSiteReadiness = createAsyncThunk< - ReadinessData, - Omit, - { - dispatch: AppDispatch; - state: RootState; - } ->('readiness/check', async (body, { dispatch }) => { - if (body.url.includes('localdev.me')) { - await fetch(body.url, { mode: 'no-cors' }); - return { success: true, url: body.url }; - } else { - const res = await dispatch(endpoints.readiness.initiate(body)); - if ('error' in res) { - throw res.error; - } - return res.data; - } -}); diff --git a/redux/thunks/settings.thunk.ts b/redux/thunks/settings.thunk.ts deleted file mode 100644 index 9ae1ce8d..00000000 --- a/redux/thunks/settings.thunk.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createAsyncThunk } from '@reduxjs/toolkit'; -import axios from 'axios'; - -import { RootState } from '../store'; - -import { createQueryString } from '@/utils/url/formatDomain'; - -type TourStatusReturn = { - 'console-tour': string; -}; - -export const getClusterTourStatus = createAsyncThunk( - 'settings/getClusterTourStatus', - async (clusterName) => { - return ( - await axios.get( - `/api/proxy?${createQueryString('url', `/secret/${clusterName}/kubefirst-state`)}`, - ) - ).data['console-tour']; - }, -); - -export const updateClusterTourStatus = createAsyncThunk< - void, - { clusterName: string; takenTour: boolean }, - { state: RootState } ->('settings/updateClusterTourStatus', async ({ clusterName, takenTour }) => { - await axios.put('/api/proxy', { - url: `/secret/${clusterName}/kubefirst-state`, - body: { - 'console-tour': `${takenTour}`, - }, - }); -}); diff --git a/redux/thunks/subscription.thunk.ts b/redux/thunks/subscription.thunk.ts deleted file mode 100644 index b2179c61..00000000 --- a/redux/thunks/subscription.thunk.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { createAsyncThunk } from '@reduxjs/toolkit'; -import axios from 'axios'; - -import { RootState } from '../store'; -import { createNotification } from '../slices/notifications.slice'; - -import { ClusterUsage, License, UserRequest } from '@/types/subscription'; - -export const validateLicenseKey = createAsyncThunk( - 'subscription/validateLicenseKey', - async () => { - return ( - await axios.post('/api/proxy?target=ee', { - url: `/subscription/validate`, - }) - ).data; - }, -); - -export const activateLicenseKey = createAsyncThunk< - License, - string, - { - state: RootState; - } ->('subscription/activateLicenseKey', async (licenseKey, { getState }) => { - const { managementCluster } = getState().api; - - return ( - await axios.post('/api/proxy?target=ee', { - url: `/subscription/${managementCluster?.clusterId}/activateCluster`, - body: { - licenseKey, - }, - }) - ).data; -}); - -export const getClusterUsage = createAsyncThunk< - Array, - string, - { - state: RootState; - } ->('subscription/getCluserUsage', async (licenseKey) => { - return ( - await axios.post>('/api/proxy?target=ee', { - url: `/subscription/clusterUsage`, - body: { - licenseKey, - }, - }) - ).data; -}); - -export const createUserRequest = createAsyncThunk< - void, - UserRequest, - { - state: RootState; - } ->('subscription/createUserRequest', async (userRequest, { dispatch }) => { - await axios.post('/api/proxy?target=ee', { - url: '/subscription/user-request', - body: { - ...userRequest, - }, - }); - - dispatch( - createNotification({ - message: 'Your message is on it’s way', - type: 'success', - snackBarOrigin: { - vertical: 'bottom', - horizontal: 'right', - }, - }), - ); -}); diff --git a/tests/mocks/mockClusterResponse.ts b/tests/mocks/mockClusterResponse.ts index 715c1d9a..06752d26 100644 --- a/tests/mocks/mockClusterResponse.ts +++ b/tests/mocks/mockClusterResponse.ts @@ -1,6 +1,6 @@ -import { GitProvider } from '@/types'; -import { ClusterResponse, ClusterStatus, ClusterType } from '@/types/provision'; -import { InstallationType } from '@/types/redux'; +import { GitProvider } from '../../types'; +import { ClusterResponse, ClusterStatus, ClusterType } from '../../types/provision'; +import { InstallationType } from '../../types/redux'; export const mockClusterResponse: ClusterResponse = { _id: '64c2ec0b057c0e84e1738aaa', @@ -36,12 +36,6 @@ export const mockClusterResponse: ClusterResponse = { cloud_region: 'LON1', instance_size: '8 GPU', node_count: 3, - environment: { - _id: '1', - creation_timestamp: '1693932566', - name: 'preprod', - color: 'gold', - }, status: ClusterStatus.PROVISIONING, cluster_type: ClusterType.WORKLOAD_V_CLUSTER, git_auth: { @@ -63,12 +57,6 @@ export const mockClusterResponse: ClusterResponse = { cloud_region: 'LON1', instance_size: '8 GPU', node_count: 3, - environment: { - _id: '2', - creation_timestamp: '1693932566', - name: 'preprod', - color: 'gold', - }, status: ClusterStatus.PROVISIONED, cluster_type: ClusterType.WORKLOAD, git_auth: { @@ -90,12 +78,6 @@ export const mockClusterResponse: ClusterResponse = { cloud_region: 'LON1', instance_size: '8 GPU', node_count: 3, - environment: { - _id: '3', - creation_timestamp: '1693932566', - name: 'preprod', - color: 'gold', - }, status: ClusterStatus.PROVISIONED, cluster_type: ClusterType.WORKLOAD, git_auth: { @@ -117,12 +99,6 @@ export const mockClusterResponse: ClusterResponse = { cloud_region: 'LON1', instance_size: '8 GPU', node_count: 3, - environment: { - _id: '4', - creation_timestamp: '1693932566', - name: 'preprod', - color: 'gold', - }, status: ClusterStatus.PROVISIONED, cluster_type: ClusterType.WORKLOAD, git_auth: { diff --git a/tests/mocks/mockUserLicense.ts b/tests/mocks/mockUserLicense.ts deleted file mode 100644 index 493c385b..00000000 --- a/tests/mocks/mockUserLicense.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { License } from '../../types/license'; - -export const mockUserLicense: License = { - key: '0e5ba61e-88b3-11ee-b9d1-0242ac120002', -}; diff --git a/types/applications/index.ts b/types/applications/index.ts deleted file mode 100644 index 50e699f1..00000000 --- a/types/applications/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { FieldValues } from 'react-hook-form'; - -export enum AppCategory { - APP_MANAGEMENT = 'App Management', - ARCHITECTURE = 'Architecture', - CI_CD = 'CI/CD', - DATABASE = 'Database', - FIN_OPS = 'FinOps', - INFRASTRUCTURE = 'Infrastructure', - MONITORING = 'Monitoring', - OBSERVABIILITY = 'Observability', - SECURITY = 'Security', - STORAGE = 'Storage', - TESTING = 'Testing', - QUEUEING = 'Queueing', - KUBESHOP = 'Kubeshop', - APPLICATIONS = 'Applications', -} - -export interface GitOpsCatalogApp { - name: string; - display_name: string; - secret_keys?: Array<{ name: string; label: string }>; - config_keys?: Array<{ name: string; label: string }>; - image_url: string; - description?: string; - category?: AppCategory; -} - -export interface ClusterApplication { - name: string; - default: boolean; - description: string; - image: string; - links: Array; - status?: string; -} - -export interface GitOpsCatalogProps { - app: GitOpsCatalogApp; - clusterName: string; - values?: FieldValues; - user: string; -} - -export interface ValidateGitOpsCatalog { - can_delete_service: boolean; -} - -export enum Target { - TEMPLATE = 'Template', - CLUSTER = 'Cluster', -} diff --git a/types/config/index.ts b/types/config/index.ts index 6836b63b..061c2f33 100644 --- a/types/config/index.ts +++ b/types/config/index.ts @@ -18,7 +18,6 @@ export interface EnvironmentVariables { isClusterZero: boolean; kubefirstVersion?: string; installMethod?: string; - saasURL?: string; } export enum ClusterManagementTab { diff --git a/types/plan/index.ts b/types/plan/index.ts deleted file mode 100644 index 992ceaf9..00000000 --- a/types/plan/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -export interface Plan { - id: string; - object: string; - active: true; - attributes: []; - created: number; - default_price: string; - description: string; - features: Array<{ - name: string; - }>; - images: Array; - livemode: false; - metadata: { - [key: string]: string; - }; - name: string; - tax_code: string; - type: string; - unit_label: string | null; - updated: number; - url: string | null; -} diff --git a/types/provision/index.ts b/types/provision/index.ts index 431cabb4..e105d424 100644 --- a/types/provision/index.ts +++ b/types/provision/index.ts @@ -1,7 +1,5 @@ import { GitProvider, Row } from '../'; -import { AdvancedOptions, InstallationType } from '../redux'; - -import { TagColor } from '@/components/Tag/Tag'; +import { InstallationType } from '../redux'; export enum ClusterStatus { DELETED = 'deleted', @@ -18,22 +16,6 @@ export enum ClusterType { } export const CLUSTER_TYPES = Object.values(ClusterType); -export const WORKLOAD_CLUSTER_TYPES = CLUSTER_TYPES.filter( - (type) => type !== ClusterType.MANAGEMENT, -); - -export type ClusterEnvironment = { - id: string; - name: string; - color: TagColor; - description?: string; - creationDate: string; -}; - -export type EnvironmentResponse = Omit & { - _id: string; - creation_timestamp: string; -}; export enum ClusterCreationStep { CONFIG, @@ -45,11 +27,6 @@ export enum ImageRepository { ECR = 'ecr', } -export type NewWorkloadClusterConfig = Partial< - Pick -> & - AdvancedOptions & { environment?: Partial }; - export interface ClusterRequestProps { clusterName?: string; } @@ -85,7 +62,6 @@ export interface ClusterResponse { domain_name: string; subdomain_name: string; dns_provider: string; - environment?: EnvironmentResponse; git_auth: { git_owner?: string; git_token?: string; @@ -159,7 +135,6 @@ export interface Cluster { domainName: string; subDomainName?: string; dnsProvider: string; - environment?: ClusterEnvironment; gitProvider: GitProvider; instanceSize?: string; nodeCount?: number; @@ -174,7 +149,6 @@ export interface Cluster { export interface ManagementCluster extends Cluster, Row { lastErrorCondition: string; - workloadClusters: WorkloadCluster[]; gitHost: string; vaultAuth: { kbotPassword: string; @@ -221,15 +195,6 @@ export interface ManagementCluster extends Cluster, Row { }; } -export interface WorkloadCluster extends Cluster { - instanceSize?: string; - machineType?: string; -} - -export type DraftCluster = Omit & { - environment?: Partial; -}; - export interface ClusterQueue { clusterName: string; status: ClusterStatus; diff --git a/types/redux/index.ts b/types/redux/index.ts index 0a68f32a..02dba6ec 100644 --- a/types/redux/index.ts +++ b/types/redux/index.ts @@ -1,4 +1,4 @@ -import { Cluster, ClusterEnvironment, DraftCluster, ImageRepository } from '../provision'; +import { ImageRepository } from '../provision'; export interface GitValues { gitToken?: string; @@ -105,8 +105,6 @@ export type AuthKeys = { name: string; label: string; helperText?: string; + defaultValue?: string; }>; }; - -export type EnvCache = Record; -export type ClusterCache = Record; diff --git a/types/subscription/index.ts b/types/subscription/index.ts deleted file mode 100644 index 0832ef89..00000000 --- a/types/subscription/index.ts +++ /dev/null @@ -1,103 +0,0 @@ -export enum SaasPlans { - Community = 'Community', - Pro = 'Pro', - Enterprise = 'Enterprise', -} - -export enum SaasFeatures { - WorkloadClustersLimit = 'workloadClustersLimit', -} - -export enum LicenseStatus { - Active = 'active', - UpToDate = 'up-todate', - PaymentFailed = 'payment-failed', - PaymentActionRequired = 'payment-action-required', -} - -export interface License { - id: string; - plan: Plan; - is_active: boolean; - status: LicenseStatus; - /** - * Price id from stripe - */ - priceId: string; - licenseKey: string; - created_at: Date; - clusters: Cluster[]; - invoices: Invoice[]; - requests: UserRequest[]; -} - -export interface Plan { - id: string; - name: string; - features: PlanFeatures[]; - licenses: License[]; -} - -export interface ClusterLimitPlan { - limit: number; -} -export interface PlanFeatures { - id: string; - code: string; - name: string; - data: ClusterLimitPlan; - plan: Plan; - isActive: boolean; -} - -export interface Cluster { - id: string; - clusterType: string; - isActive: boolean; - clusterName: string; - clusterID: string; - domainName: string; - environment: string; - deletedAt: Date; - createdAt: Date; -} - -export interface CancelSubscriptionFields { - projectIsComplete: boolean; - kubefirstIsTooExpensive: boolean; - kubefirstIsDifficult: boolean; - didnotUsePaidPlan: boolean; - didnotProvideFunctionality: boolean; - other: boolean; - description: string; -} - -export interface ContactUsFields { - firstName: string; - lastName: string; - email: string; - message: string; -} - -export interface ClusterUsage { - clusterName: string; - clusterID: string; - clusterType: string; - createdAt: Date; - deletedAt: Date; - hours: number; - total: number; -} - -export interface Invoice { - id: string; - hosted_invoice_url: string; - status: string; -} - -export interface UserRequest { - createdAt?: string; - id?: string; - type: string; - requestData: string; -} diff --git a/utils/createEnvMap.ts b/utils/createEnvMap.ts deleted file mode 100644 index 7a569ef2..00000000 --- a/utils/createEnvMap.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { EnvMap } from '../redux/slices/environments.slice'; -import { EnvironmentResponse } from '../types/provision'; - -import { mapEnvironmentFromRaw } from './mapEnvironmentFromRaw'; - -export function createEnvMap(environments: EnvironmentResponse[]): EnvMap { - return environments.reduce((acc, curVal) => { - acc[curVal._id] = mapEnvironmentFromRaw(curVal); - return acc; - }, {}); -} diff --git a/utils/getPreviouslyUsedClusterNames.ts b/utils/getPreviouslyUsedClusterNames.ts deleted file mode 100644 index 74d81c93..00000000 --- a/utils/getPreviouslyUsedClusterNames.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ManagementCluster } from '../types/provision'; - -export function getPreviouslyUsedClusterNames(cluster: ManagementCluster): string[] { - const { workloadClusters, ...managementCluster } = cluster; - return [...workloadClusters, managementCluster].reduce((acc, currentCluster) => { - const { clusterName } = currentCluster; - if (clusterName && !acc.includes(clusterName)) { - acc.push(clusterName); - } - return acc; - }, []); -} diff --git a/utils/mapClustersFromRaw.ts b/utils/mapClustersFromRaw.ts index f0883ebb..4c724f0a 100644 --- a/utils/mapClustersFromRaw.ts +++ b/utils/mapClustersFromRaw.ts @@ -1,13 +1,7 @@ -import { - ClusterResponse, - ManagementCluster, - WorkloadCluster, - ClusterStatus, -} from '../types/provision'; -import { ClusterCache, EnvCache } from '../types/redux'; +import { ClusterResponse, ManagementCluster } from '@/types/provision'; -export const mapClusterFromRaw = (cluster: ClusterResponse) => { - const managementCluster: ManagementCluster = { +export const mapClusterFromRaw = (cluster: ClusterResponse): ManagementCluster => { + return { id: cluster._id, clusterId: cluster.cluster_id, clusterName: cluster.cluster_name, @@ -63,65 +57,4 @@ export const mapClusterFromRaw = (cluster: ClusterResponse) => { users_terraform_apply_check: cluster.users_terraform_apply_check, }, }; - - const { envCache, workloadClusters, clusterCache } = [ - ...(cluster.workload_clusters ?? []), - ].reduce<{ - envCache: EnvCache; - clusterCache: ClusterCache; - workloadClusters: WorkloadCluster[]; - }>( - (acc, curVal) => { - const formattedWorkloadCluster: WorkloadCluster = { - clusterId: curVal.cluster_id, - clusterName: curVal.cluster_name, - cloudRegion: curVal.cloud_region, - cloudProvider: curVal.cloud_provider, - dnsProvider: curVal.dns_provider, - nodeCount: curVal.node_count, - instanceSize: curVal.node_type, - creationDate: curVal.creation_timestamp, - environment: { - id: curVal.environment?._id ?? '', - name: curVal.environment?.name ?? '', - creationDate: curVal.environment?.creation_timestamp ?? '', - color: curVal.environment?.color ?? 'gray', - }, - status: curVal.status, - type: curVal.cluster_type, - domainName: curVal.domain_name, - subDomainName: cluster.subdomain_name, // take subdomain from management since we do not store it on the workload cluster - gitProvider: cluster.git_provider, - adminEmail: cluster.alerts_email, - gitAuth: { - gitOwner: curVal.git_auth.git_owner, - gitToken: curVal.git_auth.git_token, - gitUser: curVal.git_auth.git_username, - }, - }; - - acc.workloadClusters.push(formattedWorkloadCluster); - - if ( - curVal.environment && - curVal.environment.name && - curVal.status !== ClusterStatus.DELETED - ) { - acc.envCache[curVal.environment.name] = true; - } - - acc.clusterCache[curVal.cluster_name] = formattedWorkloadCluster; - return acc; - }, - { clusterCache: {}, envCache: {}, workloadClusters: [] }, - ); - - managementCluster.workloadClusters = workloadClusters; - clusterCache[managementCluster.clusterName] = managementCluster; - - return { - managementCluster, - envCache, - clusterCache, - }; }; diff --git a/utils/mapEnvironmentFromRaw.ts b/utils/mapEnvironmentFromRaw.ts deleted file mode 100644 index 65167818..00000000 --- a/utils/mapEnvironmentFromRaw.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { EnvironmentResponse, ClusterEnvironment } from '../types/provision'; - -export const mapEnvironmentFromRaw = ({ - creation_timestamp, - _id, - ...rest -}: EnvironmentResponse): ClusterEnvironment => ({ - ...rest, - id: _id, - creationDate: creation_timestamp, -}); diff --git a/utils/reactFlow/index.ts b/utils/reactFlow/index.ts deleted file mode 100644 index d6b96c69..00000000 --- a/utils/reactFlow/index.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { Edge } from 'reactflow'; - -import { - ManagementCluster, - ClusterStatus, - Cluster, - ClusterType, - DraftCluster, -} from '@/types/provision'; -import { ClusterCache } from '@/types/redux'; -import { CustomGraphNode } from '@/components/GraphNode/GraphNode'; -import { RESERVED_DRAFT_CLUSTER_NAME } from '@/constants'; - -const WORKLOAD_CLUSTER_Y_SPACE = 60; -const WORKLOAD_CLUSTER_X_SPACE = 250; -const WORKLOAD_NODE_HEIGHT = 128; -const MANAGEMENT_NODE_HEIGHT = 97; -const NODE_WIDTH = 360; - -export function generateNode( - id: string, - position: { x: number; y: number }, - info: Cluster | DraftCluster, - selected = false, - className?: string, -): CustomGraphNode { - return { - id, - type: 'custom', - data: info, - position, - draggable: false, - selected, - className, - }; -} - -export function generateEdge(id: string, source: string, target: string, animated = false): Edge { - return { - id, - source, - target, - animated, - type: 'straight', - style: { strokeWidth: 2, stroke: '#CBD5E1' }, - }; -} - -export function generateNodesConfig( - managementCluster: ManagementCluster, - clusters: ClusterCache, -): [CustomGraphNode[], Edge[]] { - const { clusterId: managementClusterId } = managementCluster; - - const filteredWorkloadClusters = Object.values(clusters).filter( - (cluster) => - cluster.status !== ClusterStatus.DELETED && cluster.type !== ClusterType.MANAGEMENT, - ); - - const workloadClusterLength = filteredWorkloadClusters.length; - const spacesBetweenClusterNodes = workloadClusterLength - 1; - - // get total height of all nodes and space inbetween - const totalHeight = - workloadClusterLength * WORKLOAD_NODE_HEIGHT + - spacesBetweenClusterNodes * WORKLOAD_CLUSTER_Y_SPACE; - - // place the middle of the node at the top of the column - const initialClusterYPosition = -(totalHeight / 2) + MANAGEMENT_NODE_HEIGHT / 2; - - const nodes: CustomGraphNode[] = [ - generateNode( - managementClusterId, - { - x: 0, - y: 0, - }, - managementCluster, - false, - 'management-cluster', - ), - ]; - - const edges: Edge[] = []; - - for (let i = 0; i < workloadClusterLength; i += 1) { - const workloadCluster = filteredWorkloadClusters[i]; - const { clusterId: workloadClusterId } = workloadCluster; - - // if first node place at initial position - // otherwise add workload node height and space multiplied by index - const nodeYPosition = !i - ? initialClusterYPosition - : initialClusterYPosition + (WORKLOAD_CLUSTER_Y_SPACE + WORKLOAD_NODE_HEIGHT) * i; - - const animatedEdge = - workloadCluster.clusterId === RESERVED_DRAFT_CLUSTER_NAME || - workloadCluster.status === ClusterStatus.PROVISIONING; - - nodes.push( - generateNode( - workloadClusterId, - { x: WORKLOAD_CLUSTER_X_SPACE + NODE_WIDTH, y: nodeYPosition }, - workloadCluster, - false, - `workload-cluster-${i + 1}`, - ), - ); - - edges.push( - generateEdge( - `edge-${workloadClusterId}`, - managementClusterId, - workloadClusterId, - animatedEdge, - ), - ); - } - - return [nodes, edges]; -}