diff --git a/frontend/.gitignore b/frontend/.gitignore
index 7d5390b6df..10bbc1c1fa 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -49,6 +49,7 @@ build
.DS_Store
+cypress/downloads/*
cypress/videos/*
cypress/downloads/*
cypress/screenshots/*
diff --git a/frontend/package.json b/frontend/package.json
index 673075095a..b1ede8ce59 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "unleash-frontend",
"description": "unleash your features",
- "version": "4.14.0-beta.4",
+ "version": "4.14.0-beta.5",
"keywords": [
"unleash",
"feature toggle",
diff --git a/frontend/src/component/App.tsx b/frontend/src/component/App.tsx
index 3313833635..62c4d55233 100644
--- a/frontend/src/component/App.tsx
+++ b/frontend/src/component/App.tsx
@@ -14,6 +14,7 @@ import { SplashPageRedirect } from 'component/splash/SplashPageRedirect/SplashPa
import { useStyles } from './App.styles';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
+import { Suspense } from 'react';
export const App = () => {
const { classes: styles } = useStyles();
@@ -30,37 +31,39 @@ export const App = () => {
return (
- }
- elseShow={
-
-
-
-
- {availableRoutes.map(route => (
+ }>
+ }
+ elseShow={
+
+
+
+
+ {availableRoutes.map(route => (
+
+ }
+ />
+ ))}
+
}
/>
- ))}
-
- }
- />
- } />
-
-
-
-
-
- }
- />
+ } />
+
+
+
+
+
+ }
+ />
+
);
};
diff --git a/frontend/src/component/admin/billing/Billing.tsx b/frontend/src/component/admin/billing/Billing.tsx
index c57975f5a1..2e82f5d8aa 100644
--- a/frontend/src/component/admin/billing/Billing.tsx
+++ b/frontend/src/component/admin/billing/Billing.tsx
@@ -1,6 +1,6 @@
import AdminMenu from '../menu/AdminMenu';
import { PageContent } from 'component/common/PageContent/PageContent';
-import { useContext } from 'react';
+import { useContext, useEffect } from 'react';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import AccessContext from 'contexts/AccessContext';
@@ -12,14 +12,28 @@ import { BillingHistory } from './BillingHistory/BillingHistory';
import useInvoices from 'hooks/api/getters/useInvoices/useInvoices';
export const Billing = () => {
- const { instanceStatus, isBilling } = useInstanceStatus();
+ const {
+ instanceStatus,
+ isBilling,
+ refetchInstanceStatus,
+ refresh,
+ loading,
+ } = useInstanceStatus();
const { invoices } = useInvoices();
const { hasAccess } = useContext(AccessContext);
+ useEffect(() => {
+ const hardRefresh = async () => {
+ await refresh();
+ refetchInstanceStatus();
+ };
+ hardRefresh();
+ }, [refetchInstanceStatus, refresh]);
+
return (
-
+
= ({
return (
- Billing Information
+ Billing information
= ({
{inactive
? 'Once we have received your billing information we will upgrade your trial within 1 business day'
- : 'These changes may take up to 1 business day and they will be visible on your next invoice'}
+ : 'Update your credit card and business information and change which email address we send invoices to'}
diff --git a/frontend/src/component/common/StringTruncator/StringTruncator.tsx b/frontend/src/component/common/StringTruncator/StringTruncator.tsx
index a797c743ca..74205701f8 100644
--- a/frontend/src/component/common/StringTruncator/StringTruncator.tsx
+++ b/frontend/src/component/common/StringTruncator/StringTruncator.tsx
@@ -17,7 +17,7 @@ const StringTruncator = ({
}: IStringTruncatorProps) => {
return (
maxLength}
+ condition={(text?.length ?? 0) > maxLength}
show={
import('./Playground'));
diff --git a/frontend/src/component/playground/Playground/Playground.tsx b/frontend/src/component/playground/Playground/Playground.tsx
index 2f6943abdd..50c8bb72bd 100644
--- a/frontend/src/component/playground/Playground/Playground.tsx
+++ b/frontend/src/component/playground/Playground/Playground.tsx
@@ -212,3 +212,5 @@ export const Playground: VFC<{}> = () => {
);
};
+
+export default Playground;
diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx
index 73ff6c6fb3..5f033a65da 100644
--- a/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx
+++ b/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx
@@ -1,4 +1,4 @@
-import { FormEvent, useEffect, useMemo, useState } from 'react';
+import React, { FormEvent, useEffect, useMemo, useState } from 'react';
import {
Autocomplete,
Button,
diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx
index edb500bb84..fd75a7224a 100644
--- a/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx
+++ b/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx
@@ -32,6 +32,7 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { IUser } from 'interfaces/user';
import { IGroup } from 'interfaces/group';
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
+import { mapGroupUsers } from '../../../../hooks/api/getters/useGroup/useGroup';
const StyledAvatar = styled(Avatar)(({ theme }) => ({
width: theme.spacing(4),
@@ -339,7 +340,6 @@ export const ProjectAccessTable: VFC = () => {
setRemoveOpen(false);
setSelectedRow(undefined);
};
-
return (
{
}
};
- const addUserToRole = async (
- projectId: string,
- roleId: number,
- userId: number
- ) => {
- const path = `api/admin/projects/${projectId}/users/${userId}/roles/${roleId}`;
- const req = createRequest(path, { method: 'POST' });
-
- try {
- const res = await makeRequest(req.caller, req.id);
-
- return res;
- } catch (e) {
- throw e;
- }
- };
-
const addAccessToProject = async (
projectId: string,
roleId: number,
@@ -226,7 +209,6 @@ const useProjectApi = () => {
deleteProject,
addEnvironmentToProject,
removeEnvironmentFromProject,
- addUserToRole,
addAccessToProject,
removeUserFromRole,
removeGroupFromRole,
diff --git a/frontend/src/hooks/api/getters/useInstanceStatus/useInstanceStatus.ts b/frontend/src/hooks/api/getters/useInstanceStatus/useInstanceStatus.ts
index 48e0279a0e..e483cfe02b 100644
--- a/frontend/src/hooks/api/getters/useInstanceStatus/useInstanceStatus.ts
+++ b/frontend/src/hooks/api/getters/useInstanceStatus/useInstanceStatus.ts
@@ -7,6 +7,7 @@ import { useEffect } from 'react';
export interface IUseInstanceStatusOutput {
instanceStatus?: IInstanceStatus;
refetchInstanceStatus: () => void;
+ refresh: () => Promise;
isBilling: boolean;
loading: boolean;
error?: Error;
@@ -33,9 +34,14 @@ export const useInstanceStatus = (): IUseInstanceStatusOutput => {
InstancePlan.TEAM,
];
+ const refresh = async (): Promise => {
+ await fetch(formatApiPath('api/instance/refresh'));
+ };
+
return {
instanceStatus: data,
refetchInstanceStatus: refetch,
+ refresh,
isBilling: billingPlans.includes(data?.plan ?? InstancePlan.UNKNOWN),
loading,
error,
diff --git a/frontend/src/hooks/api/getters/useProjectAccess/useProjectAccess.ts b/frontend/src/hooks/api/getters/useProjectAccess/useProjectAccess.ts
index a3ceea8a32..9d67f266eb 100644
--- a/frontend/src/hooks/api/getters/useProjectAccess/useProjectAccess.ts
+++ b/frontend/src/hooks/api/getters/useProjectAccess/useProjectAccess.ts
@@ -1,10 +1,12 @@
import useSWR, { mutate, SWRConfiguration } from 'swr';
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useMemo } from 'react';
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';
import { IProjectRole } from 'interfaces/role';
import { IGroup } from 'interfaces/group';
import { IUser } from 'interfaces/user';
+import { useGroups } from '../useGroups/useGroups';
+import { mapGroupUsers } from '../useGroup/useGroup';
export enum ENTITY_TYPE {
USER = 'USERS',
@@ -45,11 +47,7 @@ const useProjectAccess = (
const CACHE_KEY = `api/admin/projects/${projectId}/users`;
- const { data, error } = useSWR(
- CACHE_KEY,
- fetcher,
- options
- );
+ const { data, error } = useSWR(CACHE_KEY, fetcher, options);
const [loading, setLoading] = useState(!error && !data);
@@ -61,21 +59,19 @@ const useProjectAccess = (
setLoading(!error && !data);
}, [data, error]);
- // TODO: Remove this and replace `mockData` back for `data` @79. This mocks what a group looks like when returned along with the access.
- // const { groups } = useGroups();
- // const mockData = useMemo(
- // () => ({
- // ...data,
- // groups: groups?.map(group => ({
- // ...group,
- // roleId: 4,
- // })) as IProjectAccessGroup[],
- // }),
- // [data, groups]
- // );
-
+ let access: IProjectAccessOutput = data
+ ? {
+ roles: data.roles,
+ users: data.users,
+ groups:
+ data?.groups.map((group: any) => ({
+ ...group,
+ users: mapGroupUsers(group.users ?? []),
+ })) ?? [],
+ }
+ : { roles: [], users: [], groups: [] };
return {
- access: data ? data : { roles: [], users: [], groups: [] },
+ access: access,
error,
loading,
refetchProjectAccess,