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,