mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-04 00:18:01 +01:00
Merge branch 'main' into archive_table
This commit is contained in:
commit
bd4651b9ca
@ -13,15 +13,21 @@ import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
||||
import { SplashPageRedirect } from 'component/splash/SplashPageRedirect/SplashPageRedirect';
|
||||
import { useStyles } from './App.styles';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
export const App = () => {
|
||||
const { classes: styles } = useStyles();
|
||||
const { authDetails } = useAuthDetails();
|
||||
const { user } = useAuthUser();
|
||||
const { isOss } = useUiConfig();
|
||||
const isLoggedIn = Boolean(user?.id);
|
||||
const hasFetchedAuth = Boolean(authDetails || user);
|
||||
usePlausibleTracker();
|
||||
|
||||
const availableRoutes = isOss()
|
||||
? routes.filter(route => !route.enterprise)
|
||||
: routes;
|
||||
|
||||
return (
|
||||
<SWRProvider isUnauthorized={!isLoggedIn}>
|
||||
<ConditionallyRender
|
||||
@ -32,7 +38,7 @@ export const App = () => {
|
||||
<ToastRenderer />
|
||||
<LayoutPicker>
|
||||
<Routes>
|
||||
{routes.map(route => (
|
||||
{availableRoutes.map(route => (
|
||||
<Route
|
||||
key={route.path}
|
||||
path={route.path}
|
||||
|
@ -20,6 +20,7 @@ interface IPermissionIconButtonProps {
|
||||
type?: 'button';
|
||||
edge?: IconButtonProps['edge'];
|
||||
tooltipProps?: Omit<ITooltipResolverProps, 'children'>;
|
||||
sx?: IconButtonProps['sx'];
|
||||
size?: string;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ exports[`returns all baseRoutes 1`] = `
|
||||
},
|
||||
{
|
||||
"component": [Function],
|
||||
"enterprise": true,
|
||||
"menu": {},
|
||||
"parent": "/projects",
|
||||
"path": "/projects/create",
|
||||
@ -19,6 +20,7 @@ exports[`returns all baseRoutes 1`] = `
|
||||
},
|
||||
{
|
||||
"component": [Function],
|
||||
"enterprise": true,
|
||||
"menu": {},
|
||||
"parent": "/projects",
|
||||
"path": "/projects/:projectId/edit",
|
||||
|
@ -70,6 +70,7 @@ export const routes: IRoute[] = [
|
||||
title: 'Create',
|
||||
component: CreateProject,
|
||||
type: 'protected',
|
||||
enterprise: true,
|
||||
menu: {},
|
||||
},
|
||||
{
|
||||
@ -78,6 +79,7 @@ export const routes: IRoute[] = [
|
||||
title: ':projectId',
|
||||
component: EditProject,
|
||||
type: 'protected',
|
||||
enterprise: true,
|
||||
menu: {},
|
||||
},
|
||||
{
|
||||
|
@ -10,13 +10,18 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { useContext } from 'react';
|
||||
import AccessContext from 'contexts/AccessContext';
|
||||
import { Alert } from '@mui/material';
|
||||
|
||||
const EditProject = () => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
const id = useRequiredPathParam('projectId');
|
||||
const { project } = useProject(id);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
projectId,
|
||||
projectName,
|
||||
@ -68,6 +73,12 @@ const EditProject = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
const accessDeniedAlert = !hasAccess(UPDATE_PROJECT, projectId) && (
|
||||
<Alert severity="error" sx={{ mb: 4 }}>
|
||||
You do not have the required permissions to edit this project.
|
||||
</Alert>
|
||||
);
|
||||
|
||||
return (
|
||||
<FormTemplate
|
||||
loading={loading}
|
||||
@ -77,6 +88,7 @@ const EditProject = () => {
|
||||
documentationLinkLabel="Projects documentation"
|
||||
formatApiCode={formatApiCode}
|
||||
>
|
||||
{accessDeniedAlert}
|
||||
<ProjectForm
|
||||
errors={errors}
|
||||
handleSubmit={handleSubmit}
|
||||
@ -91,7 +103,10 @@ const EditProject = () => {
|
||||
clearErrors={clearErrors}
|
||||
validateProjectId={validateProjectId}
|
||||
>
|
||||
<UpdateButton permission={UPDATE_PROJECT} />
|
||||
<UpdateButton
|
||||
permission={UPDATE_PROJECT}
|
||||
projectId={projectId}
|
||||
/>
|
||||
</ProjectForm>
|
||||
</FormTemplate>
|
||||
);
|
||||
|
@ -19,6 +19,7 @@ import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
||||
import { TabPanel } from 'component/common/TabNav/TabPanel/TabPanel';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
const Project = () => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
@ -29,6 +30,7 @@ const Project = () => {
|
||||
const { setToastData } = useToast();
|
||||
const { classes: styles } = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const { isOss } = useUiConfig();
|
||||
|
||||
const basePath = `/projects/${projectId}`;
|
||||
const tabData = [
|
||||
@ -118,7 +120,8 @@ const Project = () => {
|
||||
</div>
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_PROJECT}
|
||||
projectId={project?.id}
|
||||
projectId={projectId}
|
||||
sx={{ visibility: isOss() ? 'hidden' : 'visible' }}
|
||||
onClick={() =>
|
||||
navigate(`/projects/${projectId}/edit`)
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ const ProjectInfo = ({
|
||||
}: IProjectInfoProps) => {
|
||||
const { classes: themeStyles } = useThemeStyles();
|
||||
const { classes: styles } = useStyles();
|
||||
const { uiConfig } = useUiConfig();
|
||||
const { uiConfig, isOss } = useUiConfig();
|
||||
|
||||
let link = `/admin/users`;
|
||||
|
||||
@ -52,6 +52,7 @@ const ProjectInfo = ({
|
||||
<div>
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_PROJECT}
|
||||
hidden={isOss()}
|
||||
projectId={id}
|
||||
component={Link}
|
||||
className={permissionButtonClass}
|
||||
|
@ -2,7 +2,7 @@ import { Card, Menu, MenuItem } from '@mui/material';
|
||||
import { useStyles } from './ProjectCard.styles';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import { ReactComponent as ProjectIcon } from 'assets/icons/projectIcon.svg';
|
||||
import { useState, SyntheticEvent } from 'react';
|
||||
import React, { useState, SyntheticEvent, useContext } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
||||
@ -11,8 +11,14 @@ import { Delete, Edit } from '@mui/icons-material';
|
||||
import { getProjectEditPath } from 'utils/routePathHelpers';
|
||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
||||
import {
|
||||
UPDATE_PROJECT,
|
||||
DELETE_PROJECT,
|
||||
} from 'component/providers/AccessProvider/permissions';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import AccessContext from 'contexts/AccessContext';
|
||||
import { DEFAULT_PROJECT_ID } from 'hooks/api/getters/useDefaultProject/useDefaultProjectId';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
interface IProjectCardProps {
|
||||
name: string;
|
||||
@ -32,6 +38,8 @@ export const ProjectCard = ({
|
||||
id,
|
||||
}: IProjectCardProps) => {
|
||||
const { classes } = useStyles();
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
const { isOss } = useUiConfig();
|
||||
const { refetch: refetchProjectOverview } = useProjects();
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const [showDelDialog, setShowDelDialog] = useState(false);
|
||||
@ -45,7 +53,7 @@ export const ProjectCard = ({
|
||||
setAnchorEl(e.currentTarget);
|
||||
};
|
||||
|
||||
const onRemoveProject = async (e: Event) => {
|
||||
const onRemoveProject = async (e: React.SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await deleteProject(id);
|
||||
@ -62,6 +70,9 @@ export const ProjectCard = ({
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const canDeleteProject =
|
||||
hasAccess(DELETE_PROJECT, id) && id !== DEFAULT_PROJECT_ID;
|
||||
|
||||
return (
|
||||
<Card className={classes.projectCard} onMouseEnter={onHover}>
|
||||
<div className={classes.header} data-loading>
|
||||
@ -69,6 +80,7 @@ export const ProjectCard = ({
|
||||
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_PROJECT}
|
||||
hidden={isOss()}
|
||||
projectId={id}
|
||||
data-loading
|
||||
onClick={handleClick}
|
||||
@ -85,6 +97,9 @@ export const ProjectCard = ({
|
||||
open={Boolean(anchorEl)}
|
||||
anchorEl={anchorEl}
|
||||
style={{ top: 0, left: -100 }}
|
||||
onClick={event => {
|
||||
event.preventDefault();
|
||||
}}
|
||||
onClose={(event: SyntheticEvent) => {
|
||||
event.preventDefault();
|
||||
setAnchorEl(null);
|
||||
@ -104,9 +119,12 @@ export const ProjectCard = ({
|
||||
e.preventDefault();
|
||||
setShowDelDialog(true);
|
||||
}}
|
||||
disabled={!canDeleteProject}
|
||||
>
|
||||
<Delete className={classes.icon} />
|
||||
Delete project
|
||||
{id === DEFAULT_PROJECT_ID && !canDeleteProject
|
||||
? "You can't delete the default project"
|
||||
: 'Delete project'}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
@ -136,9 +154,9 @@ export const ProjectCard = ({
|
||||
</div>
|
||||
<Dialogue
|
||||
open={showDelDialog}
|
||||
// @ts-expect-error
|
||||
onClick={onRemoveProject}
|
||||
onClose={() => {
|
||||
onClose={event => {
|
||||
event.preventDefault();
|
||||
setAnchorEl(null);
|
||||
setShowDelDialog(false);
|
||||
}}
|
||||
|
@ -118,12 +118,12 @@ export const ProjectListNew = () => {
|
||||
className={styles.cardLink}
|
||||
>
|
||||
<ProjectCard
|
||||
onHover={() => handleHover(project?.id)}
|
||||
name={project?.name}
|
||||
memberCount={project?.memberCount ?? 0}
|
||||
health={project?.health}
|
||||
id={project?.id}
|
||||
featureCount={project?.featureCount}
|
||||
onHover={() => handleHover(project.id)}
|
||||
name={project.name}
|
||||
memberCount={project.memberCount ?? 0}
|
||||
health={project.health}
|
||||
id={project.id}
|
||||
featureCount={project.featureCount}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
|
@ -11,6 +11,7 @@ export const UPDATE_CONTEXT_FIELD = 'UPDATE_CONTEXT_FIELD';
|
||||
export const DELETE_CONTEXT_FIELD = 'DELETE_CONTEXT_FIELD';
|
||||
export const CREATE_PROJECT = 'CREATE_PROJECT';
|
||||
export const UPDATE_PROJECT = 'UPDATE_PROJECT';
|
||||
export const DELETE_PROJECT = 'DELETE_PROJECT';
|
||||
export const DELETE_TAG_TYPE = 'DELETE_TAG_TYPE';
|
||||
export const UPDATE_TAG_TYPE = 'UPDATE_TAG_TYPE';
|
||||
export const CREATE_ADDON = 'CREATE_ADDON';
|
||||
|
@ -1,7 +1,8 @@
|
||||
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
||||
|
||||
const DEFAULT_PROJECT_ID = 'default';
|
||||
export const DEFAULT_PROJECT_ID = 'default';
|
||||
|
||||
// Get the default project ID, or the first ID if there is no default project.
|
||||
export const useDefaultProjectId = (): string | undefined => {
|
||||
const { projects = [] } = useProjects();
|
||||
|
||||
|
@ -8,6 +8,7 @@ export interface IRoute {
|
||||
parent?: string;
|
||||
flag?: string;
|
||||
hidden?: boolean;
|
||||
enterprise?: boolean;
|
||||
component: VoidFunctionComponent;
|
||||
menu: IRouteMenu;
|
||||
}
|
||||
|
@ -265,5 +265,14 @@ export default createTheme({
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiMenuItem: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.66,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user