refactor(client): remove layoutv2 abstraction
This commit is contained in:
parent
5ff7451267
commit
a47606b472
7 changed files with 38 additions and 132 deletions
|
@ -1,98 +0,0 @@
|
|||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import React, { useEffect } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
import semver from 'semver';
|
||||
import { Header } from '../ui/Header';
|
||||
import styles from './Layout.module.scss';
|
||||
import { ErrorPage } from '../ui/ErrorPage';
|
||||
import { trpc } from '../../utils/trpc';
|
||||
|
||||
interface IProps {
|
||||
loading?: boolean;
|
||||
loadingComponent?: React.ReactNode;
|
||||
error?: string;
|
||||
breadcrumbs?: { name: string; href: string; current?: boolean }[];
|
||||
children: React.ReactNode;
|
||||
title?: string;
|
||||
actions?: React.ReactNode;
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
export const Layout: React.FC<IProps> = ({ children, breadcrumbs, title, actions, loading, error, loadingComponent, data }) => {
|
||||
const refreshToken = trpc.auth.refreshToken.useMutation({
|
||||
onSuccess: (d) => {
|
||||
if (d?.token) localStorage.setItem('token', d.token);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
refreshToken.mutate();
|
||||
}, []);
|
||||
|
||||
const { data: dataVersion } = trpc.system.getVersion.useQuery(undefined, { networkMode: 'online' });
|
||||
|
||||
const defaultVersion = '0.0.0';
|
||||
const isLatest = semver.gte(dataVersion?.current || defaultVersion, dataVersion?.latest || defaultVersion);
|
||||
|
||||
const renderBreadcrumbs = () => {
|
||||
if (!breadcrumbs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ol className="breadcrumb" aria-label="breadcrumbs">
|
||||
{breadcrumbs.map((breadcrumb) => (
|
||||
<li key={breadcrumb.name} data-testid="breadcrumb-item" className={clsx('breadcrumb-item', { active: breadcrumb.current })}>
|
||||
<Link data-testid="breadcrumb-link" href={breadcrumb.href}>
|
||||
{breadcrumb.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
if (loading) {
|
||||
return loadingComponent;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorPage error={error} />;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div data-testid={`${title?.toLowerCase().split(' ').join('-')}-layout`} className="page">
|
||||
<Head>
|
||||
<title>{title} - Tipi</title>
|
||||
</Head>
|
||||
<ReactTooltip offset={{ right: 1 }} effect="solid" place="bottom" />
|
||||
<Header isUpdateAvailable={!isLatest} />
|
||||
<div className="page-wrapper">
|
||||
<div className="page-header d-print-none">
|
||||
<div className="container-xl">
|
||||
<div className={clsx('align-items-stretch align-items-md-center d-flex flex-column flex-md-row ', styles.topActions)}>
|
||||
<div className="me-3 text-white">
|
||||
<div className="page-pretitle">{renderBreadcrumbs()}</div>
|
||||
<h2 className="page-title">{title}</h2>
|
||||
</div>
|
||||
<div className="flex-fill">{actions}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="page-body">
|
||||
<div className="container-xl">{renderContent()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,13 +1,11 @@
|
|||
import { IconCircuitResistor, IconCpu, IconDatabase } from '@tabler/icons';
|
||||
import React from 'react';
|
||||
import { Layout } from '../../../components/Layout/LayoutV2';
|
||||
import { SystemRouterOutput } from '../../../../server/routers/system/system.router';
|
||||
import SystemStat from '../components/SystemStat';
|
||||
import { ContainerProps } from '../../../types/layout-helpers';
|
||||
|
||||
type IProps = { data?: SystemRouterOutput['systemInfo'] };
|
||||
type IProps = { data: SystemRouterOutput['systemInfo'] };
|
||||
|
||||
const DashboardWithData: React.FC<Required<IProps>> = ({ data }) => {
|
||||
export const DashboardContainer: React.FC<IProps> = ({ data }) => {
|
||||
const { disk, memory, cpu } = data;
|
||||
// Convert bytes to GB
|
||||
const diskFree = Math.round(disk.available / 1024 / 1024 / 1024);
|
||||
|
@ -27,9 +25,3 @@ const DashboardWithData: React.FC<Required<IProps>> = ({ data }) => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DashboardContainer: React.FC<ContainerProps<IProps>> = ({ data, loading, error }) => (
|
||||
<Layout data={data} loading={loading} error={error} title="Dashboard">
|
||||
<DashboardWithData data={data!} />
|
||||
</Layout>
|
||||
);
|
||||
|
|
|
@ -2,9 +2,16 @@ import React from 'react';
|
|||
import type { NextPage } from 'next';
|
||||
import { DashboardContainer } from '../../containers/DashboardContainer';
|
||||
import { trpc } from '../../../../utils/trpc';
|
||||
import { Layout } from '../../../../components/Layout';
|
||||
import { ErrorPage } from '../../../../components/ui/ErrorPage';
|
||||
|
||||
export const DashboardPage: NextPage = () => {
|
||||
const { data, isLoading, error } = trpc.system.systemInfo.useQuery();
|
||||
const { data, error } = trpc.system.systemInfo.useQuery();
|
||||
|
||||
return <DashboardContainer data={data} loading={isLoading} error={error?.message} />;
|
||||
return (
|
||||
<Layout title="Dashboard">
|
||||
{data && <DashboardContainer data={data} />}
|
||||
{error && <ErrorPage error={error.message} />}
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -39,15 +39,6 @@ describe('Test: SettingsContainer', () => {
|
|||
expect(screen.getByText(`Update to ${latest}`)).toBeInTheDocument();
|
||||
expect(screen.getByText(`Update to ${latest}`)).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('should display error page if error is present', () => {
|
||||
const current = faker.system.semver();
|
||||
const error = faker.lorem.sentence();
|
||||
|
||||
render(<SettingsContainer data={{ current }} error={error} />);
|
||||
|
||||
expect(screen.getByText(error)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update', () => {
|
||||
|
|
|
@ -6,13 +6,11 @@ import { SystemRouterOutput } from '../../../../../server/routers/system/system.
|
|||
import { useToastStore } from '../../../../state/toastStore';
|
||||
import { RestartModal } from '../../components/RestartModal';
|
||||
import { UpdateModal } from '../../components/UpdateModal/UpdateModal';
|
||||
import { Layout } from '../../../../components/Layout/LayoutV2';
|
||||
import { ContainerProps } from '../../../../types/layout-helpers';
|
||||
import { trpc } from '../../../../utils/trpc';
|
||||
|
||||
type IProps = { data?: SystemRouterOutput['getVersion'] };
|
||||
type IProps = { data: SystemRouterOutput['getVersion'] };
|
||||
|
||||
const SettingsContainerWithData: React.FC<Required<IProps>> = ({ data }) => {
|
||||
export const SettingsContainer: React.FC<IProps> = ({ data }) => {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const { current, latest } = data;
|
||||
const { addToast } = useToastStore();
|
||||
|
@ -81,7 +79,7 @@ const SettingsContainerWithData: React.FC<Required<IProps>> = ({ data }) => {
|
|||
<div className="col d-flex flex-column">
|
||||
<div className="card-body">
|
||||
<h2 className="mb-4">Actions</h2>
|
||||
<h3 className="card-title mt-4">Version</h3>
|
||||
<h3 className="card-title mt-4">Version {current}</h3>
|
||||
<p className="card-subtitle">Stay up to date with the latest version of Tipi</p>
|
||||
{renderUpdate()}
|
||||
<h3 className="card-title mt-4">Maintenance</h3>
|
||||
|
@ -97,9 +95,3 @@ const SettingsContainerWithData: React.FC<Required<IProps>> = ({ data }) => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SettingsContainer: React.FC<ContainerProps<IProps>> = ({ data, loading, error }) => (
|
||||
<Layout title="Settings" data={data} loading={loading} error={error}>
|
||||
<SettingsContainerWithData data={data!} />
|
||||
</Layout>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { faker } from '@faker-js/faker';
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from '../../../../../../tests/test-utils';
|
||||
import { getTRPCMockError } from '../../../../mocks/getTrpcMock';
|
||||
import { server } from '../../../../mocks/server';
|
||||
import { SettingsPage } from './SettingsPage';
|
||||
|
||||
describe('Test: SettingsPage', () => {
|
||||
|
@ -8,4 +11,15 @@ describe('Test: SettingsPage', () => {
|
|||
|
||||
await waitFor(() => expect(screen.getByTestId('settings-layout')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should display error page if error is present', async () => {
|
||||
const error = faker.lorem.sentence();
|
||||
server.use(getTRPCMockError({ path: ['system', 'getVersion'], message: error }));
|
||||
|
||||
render(<SettingsPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(error)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,9 +2,17 @@ import React from 'react';
|
|||
import type { NextPage } from 'next';
|
||||
import { SettingsContainer } from '../../containers/SettingsContainer/SettingsContainer';
|
||||
import { trpc } from '../../../../utils/trpc';
|
||||
import { Layout } from '../../../../components/Layout';
|
||||
import { ErrorPage } from '../../../../components/ui/ErrorPage';
|
||||
|
||||
export const SettingsPage: NextPage = () => {
|
||||
const { data, isLoading, error } = trpc.system.getVersion.useQuery(undefined, { staleTime: 0 });
|
||||
const { data, error } = trpc.system.getVersion.useQuery(undefined, { staleTime: 0 });
|
||||
|
||||
return <SettingsContainer data={data} loading={isLoading} error={error?.message} />;
|
||||
// TODO: add loading state
|
||||
return (
|
||||
<Layout title="Settings">
|
||||
{data && <SettingsContainer data={data} />}
|
||||
{error && <ErrorPage error={error.message} />}
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue