1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01:00

Merge branch 'main' into task/constraint_card_adjustmnets

This commit is contained in:
andreas-unleash 2022-07-26 13:10:45 +03:00 committed by GitHub
commit 3c23fb554e
15 changed files with 93 additions and 79 deletions

1
frontend/.gitignore vendored
View File

@ -49,6 +49,7 @@ build
.DS_Store
cypress/downloads/*
cypress/videos/*
cypress/downloads/*
cypress/screenshots/*

View File

@ -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",

View File

@ -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,6 +31,7 @@ export const App = () => {
return (
<SWRProvider isUnauthorized={!isLoggedIn}>
<Suspense fallback={<Loader />}>
<ConditionallyRender
condition={!hasFetchedAuth}
show={<Loader />}
@ -61,6 +63,7 @@ export const App = () => {
</div>
}
/>
</Suspense>
</SWRProvider>
);
};

View File

@ -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 (
<div>
<AdminMenu />
<PageContent header="Billing">
<PageContent header="Billing" isLoading={loading}>
<ConditionallyRender
condition={isBilling}
show={

View File

@ -40,7 +40,7 @@ export const BillingInformation: FC<IBillingInformationProps> = ({
return (
<Grid item xs={12} md={5}>
<StyledInfoBox>
<StyledTitle variant="body1">Billing Information</StyledTitle>
<StyledTitle variant="body1">Billing information</StyledTitle>
<ConditionallyRender
condition={inactive}
show={
@ -54,7 +54,7 @@ export const BillingInformation: FC<IBillingInformationProps> = ({
<StyledInfoLabel>
{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'}
</StyledInfoLabel>
<StyledDivider />
<StyledInfoLabel>

View File

@ -17,7 +17,7 @@ const StringTruncator = ({
}: IStringTruncatorProps) => {
return (
<ConditionallyRender
condition={text.length > maxLength}
condition={(text?.length ?? 0) > maxLength}
show={
<Tooltip title={text} arrow>
<span

View File

@ -120,7 +120,14 @@ exports[`returns all baseRoutes 1`] = `
"type": "protected",
},
{
"component": [Function],
"component": {
"$$typeof": Symbol(react.lazy),
"_init": [Function],
"_payload": {
"_result": [Function],
"_status": -1,
},
},
"hidden": false,
"menu": {
"mobile": true,
@ -473,7 +480,7 @@ exports[`returns all baseRoutes 1`] = `
},
"parent": "/admin",
"path": "/admin-invoices",
"title": "Invoices",
"title": "Billing & invoices",
"type": "protected",
},
{

View File

@ -53,10 +53,10 @@ import { SegmentTable } from 'component/segments/SegmentTable/SegmentTable';
import FlaggedBillingRedirect from 'component/admin/billing/FlaggedBillingRedirect/FlaggedBillingRedirect';
import { FeaturesArchiveTable } from '../archive/FeaturesArchiveTable';
import { Billing } from 'component/admin/billing/Billing';
import { Playground } from 'component/playground/Playground/Playground';
import { Group } from 'component/admin/groups/Group/Group';
import { CreateGroup } from 'component/admin/groups/CreateGroup/CreateGroup';
import { EditGroup } from 'component/admin/groups/EditGroup/EditGroup';
import { LazyPlayground } from 'component/playground/Playground/LazyPlayground';
export const routes: IRoute[] = [
// Splash
@ -182,7 +182,7 @@ export const routes: IRoute[] = [
{
path: '/playground',
title: 'Playground',
component: Playground,
component: LazyPlayground,
hidden: false,
type: 'protected',
menu: { mobile: true },
@ -518,7 +518,7 @@ export const routes: IRoute[] = [
{
path: '/admin-invoices',
parent: '/admin',
title: 'Invoices',
title: 'Billing & invoices',
component: FlaggedBillingRedirect,
type: 'protected',
menu: { adminSettings: true, isEnterprise: true },

View File

@ -0,0 +1,3 @@
import { lazy } from 'react';
export const LazyPlayground = lazy(() => import('./Playground'));

View File

@ -212,3 +212,5 @@ export const Playground: VFC<{}> = () => {
</PageContent>
);
};
export default Playground;

View File

@ -1,4 +1,4 @@
import { FormEvent, useEffect, useMemo, useState } from 'react';
import React, { FormEvent, useEffect, useMemo, useState } from 'react';
import {
Autocomplete,
Button,

View File

@ -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 (
<PageContent
header={

View File

@ -112,23 +112,6 @@ const useProjectApi = () => {
}
};
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,

View File

@ -7,6 +7,7 @@ import { useEffect } from 'react';
export interface IUseInstanceStatusOutput {
instanceStatus?: IInstanceStatus;
refetchInstanceStatus: () => void;
refresh: () => Promise<void>;
isBilling: boolean;
loading: boolean;
error?: Error;
@ -33,9 +34,14 @@ export const useInstanceStatus = (): IUseInstanceStatusOutput => {
InstancePlan.TEAM,
];
const refresh = async (): Promise<void> => {
await fetch(formatApiPath('api/instance/refresh'));
};
return {
instanceStatus: data,
refetchInstanceStatus: refetch,
refresh,
isBilling: billingPlans.includes(data?.plan ?? InstancePlan.UNKNOWN),
loading,
error,

View File

@ -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<IProjectAccessOutput>(
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,