diff --git a/.node-version b/.node-version
index 603606bc91..4a1f488b6c 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-18.17.0
+18.17.1
diff --git a/frontend/package.json b/frontend/package.json
index 0a99c04f6f..93cc482ee2 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -100,11 +100,11 @@
"react-joyride": "^2.5.3",
"react-linkify": "^1.0.0-alpha",
"react-markdown": "^8.0.4",
- "react-router-dom": "6.14.2",
+ "react-router-dom": "6.15.0",
"react-table": "7.8.0",
"react-test-renderer": "17.0.2",
"react-timeago": "7.1.0",
- "sass": "1.64.2",
+ "sass": "1.65.1",
"semver": "7.5.4",
"swr": "2.2.0",
"tss-react": "4.8.8",
diff --git a/frontend/src/component/admin/adminRoutes.ts b/frontend/src/component/admin/adminRoutes.ts
index ff5e7916ed..eed5467cd1 100644
--- a/frontend/src/component/admin/adminRoutes.ts
+++ b/frontend/src/component/admin/adminRoutes.ts
@@ -90,7 +90,7 @@ export const adminRoutes: INavigationMenuItem[] = [
{
path: '/admin/admin-invoices',
title: 'Billing & invoices',
- menu: { adminSettings: true, mode: ['pro'], billing: true },
+ menu: { adminSettings: true, billing: true },
group: 'instance',
},
{
diff --git a/frontend/src/component/admin/invoice/InvoiceList.tsx b/frontend/src/component/admin/invoice/InvoiceList.tsx
index 5e14509599..a690f43536 100644
--- a/frontend/src/component/admin/invoice/InvoiceList.tsx
+++ b/frontend/src/component/admin/invoice/InvoiceList.tsx
@@ -115,7 +115,9 @@ const InvoiceList = () => {
}
- elseShow={
{isLoaded && 'No invoices to show.'}
}
+ elseShow={
+ {isLoaded && 'No invoices to show.'}
+ }
/>
);
};
diff --git a/frontend/src/component/admin/menu/AdminTabsMenu.tsx b/frontend/src/component/admin/menu/AdminTabsMenu.tsx
index 685f4f76a2..81c758984b 100644
--- a/frontend/src/component/admin/menu/AdminTabsMenu.tsx
+++ b/frontend/src/component/admin/menu/AdminTabsMenu.tsx
@@ -55,6 +55,7 @@ export const AdminTabsMenu: VFC = () => {
>
{tabs.map(tab => (
{
- const navLinkStyle = {
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- width: '100%',
- textDecoration: 'none',
- color: 'inherit',
- padding: props.theme.spacing(1.5, 3),
- };
-
- const activeNavLinkStyle: React.CSSProperties = {
+const StyledNavLink = styled(NavLink)(({ theme }) => ({
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: '100%',
+ height: '100%',
+ textDecoration: 'none',
+ color: 'inherit',
+ padding: theme.spacing(0, 5),
+ '&.active': {
fontWeight: 'bold',
- borderRadius: '3px',
- padding: props.theme.spacing(1.5, 3),
- };
-
- return props.isActive
- ? { ...navLinkStyle, ...activeNavLinkStyle }
- : navLinkStyle;
-};
+ },
+}));
export const CenteredNavLink: FC<{ to: string }> = ({ to, children }) => {
- const theme = useTheme();
- return (
- createNavLinkStyle({ isActive, theme })}
- >
- {children}
-
- );
+ return {children};
};
diff --git a/frontend/src/component/admin/network/Network.tsx b/frontend/src/component/admin/network/Network.tsx
index a6fa643b24..33d32d6216 100644
--- a/frontend/src/component/admin/network/Network.tsx
+++ b/frontend/src/component/admin/network/Network.tsx
@@ -43,6 +43,7 @@ export const Network = () => {
{label}
}
+ sx={{ padding: 0 }}
/>
))}
diff --git a/frontend/src/component/admin/roles/RolesPage.tsx b/frontend/src/component/admin/roles/RolesPage.tsx
index e708b02e98..681ae9e6d5 100644
--- a/frontend/src/component/admin/roles/RolesPage.tsx
+++ b/frontend/src/component/admin/roles/RolesPage.tsx
@@ -97,6 +97,7 @@ export const RolesPage = () => {
}
+ sx={{ padding: 0 }}
/>
))}
diff --git a/frontend/src/component/admin/useAdminRoutes.ts b/frontend/src/component/admin/useAdminRoutes.ts
index 61b85f11de..1e4b529f86 100644
--- a/frontend/src/component/admin/useAdminRoutes.ts
+++ b/frontend/src/component/admin/useAdminRoutes.ts
@@ -10,8 +10,19 @@ export const useAdminRoutes = () => {
const showEnterpriseOptionsInPro = Boolean(
uiConfig?.flags?.frontendNavigationUpdate
);
+ const routes = [...adminRoutes];
- return adminRoutes
+ if (uiConfig.flags.UNLEASH_CLOUD) {
+ const adminBillingMenuItem = adminRoutes.findIndex(
+ route => route.title === 'Billing & invoices'
+ );
+ routes[adminBillingMenuItem] = {
+ ...routes[adminBillingMenuItem],
+ path: '/admin/billing',
+ };
+ }
+
+ return routes
.filter(filterByConfig(uiConfig))
.filter(route =>
filterAdminRoutes(
diff --git a/frontend/src/component/application/ApplicationList/ApplicationList.tsx b/frontend/src/component/application/ApplicationList/ApplicationList.tsx
index 19d31c7f86..d324e910c3 100644
--- a/frontend/src/component/application/ApplicationList/ApplicationList.tsx
+++ b/frontend/src/component/application/ApplicationList/ApplicationList.tsx
@@ -1,41 +1,37 @@
-import { useEffect, useMemo, useState } from 'react';
-import { CircularProgress, Link } from '@mui/material';
+import { useMemo } from 'react';
+import {
+ Avatar,
+ CircularProgress,
+ Icon,
+ Link,
+ styled,
+ Typography,
+ useTheme,
+} from '@mui/material';
import { Warning } from '@mui/icons-material';
-import { AppsLinkList, styles as themeStyles } from 'component/common';
+import { styles as themeStyles } from 'component/common';
import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import useApplications from 'hooks/api/getters/useApplications/useApplications';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
-import { useSearchParams } from 'react-router-dom';
import { Search } from 'component/common/Search/Search';
-import { safeRegExp } from '@server/util/escape-regex';
-
-type PageQueryType = Partial>;
+import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
+import {
+ SortableTableHeader,
+ Table,
+ TableBody,
+ TableCell,
+ TableRow,
+} from 'component/common/Table';
+import { useGlobalFilter, useSortBy, useTable } from 'react-table';
+import { sortTypes } from 'utils/sortTypes';
+import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
+import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
+import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
export const ApplicationList = () => {
- const { applications, loading } = useApplications();
- const [searchParams, setSearchParams] = useSearchParams();
- const [searchValue, setSearchValue] = useState(
- searchParams.get('search') || ''
- );
-
- useEffect(() => {
- const tableState: PageQueryType = {};
- if (searchValue) {
- tableState.search = searchValue;
- }
-
- setSearchParams(tableState, {
- replace: true,
- });
- }, [searchValue, setSearchParams]);
-
- const filteredApplications = useMemo(() => {
- const regExp = safeRegExp(searchValue, 'i');
- return searchValue
- ? applications?.filter(a => regExp.test(a.appName))
- : applications;
- }, [applications, searchValue]);
+ const { applications: data, loading } = useApplications();
+ const theme = useTheme();
const renderNoApplications = () => (
<>
@@ -56,25 +52,115 @@ export const ApplicationList = () => {
>
);
- if (!filteredApplications) {
+ const initialState = useMemo(
+ () => ({
+ sortBy: [{ id: 'name', desc: false }],
+ hiddenColumns: ['description', 'sortOrder'],
+ }),
+ []
+ );
+
+ const columns = useMemo(
+ () => [
+ {
+ id: 'Icon',
+ Cell: ({
+ row: {
+ original: { icon },
+ },
+ }: any) => (
+
+ {icon}
+
+ }
+ />
+ ),
+ disableGlobalFilter: true,
+ },
+ {
+ Header: 'Name',
+ accessor: 'appName',
+ width: '50%',
+ Cell: ({
+ row: {
+ original: { appName, description },
+ },
+ }: any) => (
+
+ ),
+ sortType: 'alphanumeric',
+ },
+ {
+ Header: 'Project(environment)',
+ accessor: 'usage',
+ width: '50%',
+ Cell: () => (
+
+
+ not connected
+
+
+ ),
+ sortType: 'alphanumeric',
+ },
+ {
+ accessor: 'description',
+ disableSortBy: true,
+ },
+ {
+ accessor: 'sortOrder',
+ disableGlobalFilter: true,
+ sortType: 'number',
+ },
+ ],
+ []
+ );
+
+ const {
+ getTableProps,
+ getTableBodyProps,
+ headerGroups,
+ rows,
+ prepareRow,
+ state: { globalFilter },
+ setGlobalFilter,
+ } = useTable(
+ {
+ columns: columns as any[], // TODO: fix after `react-table` v8 update
+ data,
+ initialState,
+ sortTypes,
+ autoResetGlobalFilter: false,
+ autoResetSortBy: false,
+ disableSortRemove: true,
+ },
+ useGlobalFilter,
+ useSortBy
+ );
+
+ if (!data) {
return ;
}
- let applicationCount =
- filteredApplications.length < applications.length
- ? `${filteredApplications.length} of ${applications.length}`
- : applications.length;
-
return (
<>
}
/>
@@ -82,8 +168,37 @@ export const ApplicationList = () => {
>
0}
- show={}
+ condition={data.length > 0}
+ show={
+
+
+
+
+ {rows.map(row => {
+ prepareRow(row);
+ return (
+
+ {row.cells.map(cell => (
+
+ {cell.render(
+ 'Cell'
+ )}
+
+ ))}
+
+ );
+ })}
+
+
+
+ }
elseShow={
>;
+
+export const OldApplicationList = () => {
+ const { applications, loading } = useApplications();
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [searchValue, setSearchValue] = useState(
+ searchParams.get('search') || ''
+ );
+
+ useEffect(() => {
+ const tableState: PageQueryType = {};
+ if (searchValue) {
+ tableState.search = searchValue;
+ }
+
+ setSearchParams(tableState, {
+ replace: true,
+ });
+ }, [searchValue, setSearchParams]);
+
+ const filteredApplications = useMemo(() => {
+ const regExp = safeRegExp(searchValue, 'i');
+ return searchValue
+ ? applications?.filter(a => regExp.test(a.appName))
+ : applications;
+ }, [applications, searchValue]);
+
+ const renderNoApplications = () => (
+ <>
+
+
+
+ Oh snap, it does not seem like you have connected any
+ applications. To connect your application to Unleash you will
+ require a Client SDK.
+
+
+ You can read more about how to use Unleash in your application
+ in the{' '}
+
+ documentation.
+
+
+ >
+ );
+
+ if (!filteredApplications) {
+ return ;
+ }
+
+ let applicationCount =
+ filteredApplications.length < applications.length
+ ? `${filteredApplications.length} of ${applications.length}`
+ : applications.length;
+
+ return (
+ <>
+
+ }
+ />
+ }
+ >
+
+
0}
+ show={}
+ elseShow={
+ ...loading }
+ elseShow={renderNoApplications()}
+ />
+ }
+ />
+
+
+ >
+ );
+};
diff --git a/frontend/src/component/application/ApplicationList/TemporaryApplicationListWrapper.tsx b/frontend/src/component/application/ApplicationList/TemporaryApplicationListWrapper.tsx
new file mode 100644
index 0000000000..120bd5e536
--- /dev/null
+++ b/frontend/src/component/application/ApplicationList/TemporaryApplicationListWrapper.tsx
@@ -0,0 +1,36 @@
+import { useMemo } from 'react';
+import { Avatar, CircularProgress, Icon, Link } from '@mui/material';
+import { Warning } from '@mui/icons-material';
+import { styles as themeStyles } from 'component/common';
+import { PageContent } from 'component/common/PageContent/PageContent';
+import { PageHeader } from 'component/common/PageHeader/PageHeader';
+import useApplications from 'hooks/api/getters/useApplications/useApplications';
+import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
+import { Search } from 'component/common/Search/Search';
+import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
+import {
+ SortableTableHeader,
+ Table,
+ TableBody,
+ TableCell,
+ TableRow,
+} from '../../common/Table';
+import { useGlobalFilter, useSortBy, useTable } from 'react-table';
+import { sortTypes } from 'utils/sortTypes';
+import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
+import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
+import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
+import { ApplicationList } from './ApplicationList';
+import { OldApplicationList } from './OldApplicationList';
+
+export const TemporaryApplicationListWrapper = () => {
+ const { uiConfig } = useUiConfig();
+
+ return (
+ }
+ elseShow={}
+ />
+ );
+};
diff --git a/frontend/src/component/menu/Header/NavigationMenu/NavigationMenu.tsx b/frontend/src/component/menu/Header/NavigationMenu/NavigationMenu.tsx
index da60c200cb..56b538a987 100644
--- a/frontend/src/component/menu/Header/NavigationMenu/NavigationMenu.tsx
+++ b/frontend/src/component/menu/Header/NavigationMenu/NavigationMenu.tsx
@@ -98,9 +98,9 @@ export const NavigationMenu = ({
}
arrow
placement="left"
+ key={option.path}
>