diff --git a/fbcnms-packages/fbcnms-ui/components/Colors.js b/fbcnms-packages/fbcnms-ui/components/Colors.js new file mode 100644 index 0000000..0c51491 --- /dev/null +++ b/fbcnms-packages/fbcnms-ui/components/Colors.js @@ -0,0 +1,63 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +export const colors = { + primary: { + white: '#FFFFFF', + selago: '#F4F7FD', + concrete: '#F2F2F2', + mercury: '#E5E5E5', + nobel: '#B3B3B3', + gullGray: '#9DA7BB', + comet: '#545F77', + brightGray: '#323845', + mirage: '#171B25', + }, + secondary: { + malibu: '#88B3F9', + dodgerBlue: '#3984FF', + mariner: '#1F5BC4', + }, + button: { + lightOutline: '#CCD0DB', + fill: '#FAFAFB', + }, + state: { + positive: '#31BF56', + positiveAlt: '#229A41', + error: '#E52240', + errorAlt: '#B21029', + errorFill: '#FFF8F9', + warning: '#F5DD5A', + warningAlt: '#B69900', + warningFill: '#FFFCED', + }, + alerts: { + severe: 'E52240', + major: '#E36730', + minor: '#F5DD5A', + other: '#88B3F9', + }, + data: { + coral: '#FF824B', + flamePea: '#E36730', + portage: '#A07EEA', + studio: '#6649A6', + }, + code: { + crusta: '#F76D47', + pelorous: '#39B6C8', + electricViolet: '#7D4DFF', + orchid: '#DA70D6', + chelseaCucumber: '#91B859', + candlelight: '#FFD715', + mischka: '#D4D8DE', + }, +}; diff --git a/fbcnms-packages/fbcnms-ui/components/DataGrid.js b/fbcnms-packages/fbcnms-ui/components/DataGrid.js new file mode 100644 index 0000000..f11d1f2 --- /dev/null +++ b/fbcnms-packages/fbcnms-ui/components/DataGrid.js @@ -0,0 +1,266 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ +import typeof SvgIcon from '@material-ui/core/@@SvgIcon'; + +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import Collapse from '@material-ui/core/Collapse'; +import Divider from '@material-ui/core/Divider'; +import ExpandLess from '@material-ui/icons/ExpandLess'; +import ExpandMore from '@material-ui/icons/ExpandMore'; +import Grid from '@material-ui/core/Grid'; +import IconButton from '@material-ui/core/IconButton'; +import Input from '@material-ui/core/Input'; +import InputAdornment from '@material-ui/core/InputAdornment'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import React from 'react'; +import Visibility from '@material-ui/icons/Visibility'; +import VisibilityOff from '@material-ui/icons/VisibilityOff'; + +import {colors} from './Colors'; +import {makeStyles} from '@material-ui/styles'; + +const useStyles = makeStyles(theme => ({ + dataHeaderBlock: { + display: 'flex', + alignItems: 'center', + padding: 0, + }, + dataHeaderContent: { + display: 'flex', + alignItems: 'center', + }, + dataHeaderIcon: { + fill: colors.primary.comet, + marginRight: theme.spacing(1), + }, + dataBlock: { + boxShadow: `0 0 0 1px ${colors.primary.concrete}`, + }, + dataLabel: { + color: colors.primary.comet, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + dataValue: { + color: colors.primary.brightGray, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + width: props => + props.hasStatus + ? 'calc(100% - 16px)' + : props.hasIcon + ? 'calc(100% - 32px)' + : '100%', + }, + dataObscuredValue: { + color: colors.primary.brightGray, + width: '100%', + + '& input': { + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + }, + dataBox: { + width: '100%', + padding: props => (props.collapsed ? '0' : null), + + '& > div': { + width: '100%', + }, + }, + dataIcon: { + display: 'flex', + alignItems: 'center', + + '& svg': { + fill: colors.primary.comet, + marginRight: theme.spacing(1), + }, + }, + list: { + padding: 0, + }, +})); + +// Data Icon adds an icon to the left of the value +function DataIcon(icon: SvgIcon, val: string) { + const props = {hasIcon: true}; + const classes = useStyles(props); + const Icon = icon; + return ( + + + + + + {val} + + + ); +} + +// Data Obscure makes the field into a password type filed with a visibility toggle for more sensitive fields. +function DataObscure(value: number | string, category: ?string) { + const [showPassword, setShowPassword] = React.useState(false); + return ( + + setShowPassword(!showPassword)} + onMouseDown={event => event.preventDefault()}> + {showPassword ? : } + + + } + /> + ); +} + +function DataCollapse(data: Data) { + const props = {collapsed: true}; + const classes = useStyles(props); + const [open, setOpen] = React.useState(true); + const dataEntryValue = data.value + (data.unit ?? ''); + return ( + + setOpen(!open)}> + + {open ? : } + + + + {data.collapse ?? <>} + + + ); +} + +type Data = { + icon?: SvgIcon, + category?: string, + value: number | string, + obscure?: boolean, + //$FlowFixMe TODO: Needs a ComponentType argument + collapse?: ComponentType | boolean, + unit?: string, + statusCircle?: boolean, + statusInactive?: boolean, + status?: boolean, + tooltip?: string, +}; + +export type DataRows = Data[]; + +type Props = {data: DataRows[], testID?: string}; + +export default function DataGrid(props: Props) { + const classes = useStyles(); + const dataGrid = props.data.map((row, i) => ( + + {row.map((data, j) => { + const dataEntryValue = data.value + (data.unit ?? ''); + + return ( + + + + {data.collapse !== undefined && data.collapse !== false ? ( + DataCollapse(data) + ) : ( + + )} + + + + ); + })} + + )); + return ( + + + {dataGrid} + + + ); +} diff --git a/fbcnms-packages/fbcnms-ui/master/OrganizationDialog.js b/fbcnms-packages/fbcnms-ui/master/OrganizationDialog.js index a1fbd26..d72a586 100644 --- a/fbcnms-packages/fbcnms-ui/master/OrganizationDialog.js +++ b/fbcnms-packages/fbcnms-ui/master/OrganizationDialog.js @@ -21,6 +21,7 @@ import DialogTitle from '@material-ui/core/DialogTitle'; import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormHelperText from '@material-ui/core/FormHelperText'; import FormLabel from '@material-ui/core/FormLabel'; +import Input from '@material-ui/core/Input'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemText from '@material-ui/core/ListItemText'; @@ -31,12 +32,15 @@ import React from 'react'; import Select from '@material-ui/core/Select'; import Tab from '@material-ui/core/Tab'; import Tabs from '@material-ui/core/Tabs'; +import TypedSelect from '@fbcnms/ui/components/TypedSelect'; +import renderList from '@fbcnms/util/renderList'; import {AltFormField} from '../components/FormField'; import {UserRoles} from '@fbcnms/auth/types'; +import {getProjectTabs as getAllProjectTabs} from '@fbcnms/projects/projects'; import {makeStyles} from '@material-ui/styles'; import {useAxios} from '@fbcnms/ui/hooks'; -import {useState} from 'react'; +import {useEffect, useState} from 'react'; const useStyles = makeStyles(_ => ({ input: { @@ -58,6 +62,9 @@ type Props = { onCreateUser: (user: CreateUserType) => void, addUser: boolean, setAddUser: () => void, + createError: string, + open: boolean, + organization: any, }; type CreateUserType = { @@ -85,13 +92,16 @@ export default function (props: Props) { }); const [organization, setOrganization] = useState( - {}, + props.organization || {}, ); - const [currentTab, setCurrentTab] = useState(props.addUser ? 1 : 0); + const [currentTab, setCurrentTab] = useState(0); const [shouldEnableAllNetworks, setShouldEnableAllNetworks] = useState(false); const [user, setUser] = useState({}); const [createError, setCreateError] = useState(''); const allNetworks = error || !response ? [] : response.data.sort(); + useEffect(() => { + setCurrentTab(props.addUser ? 1 : 0); + }, [props.addUser]); if (isLoading) { return ; @@ -155,12 +165,16 @@ export default function (props: Props) { : Array.from(user.networkIDs || []), }; props.onCreateUser(payload); + // console.log(createError, 'error'); + // if(createError) {} + // props.onClose(); + // setUser({}); } }; return ( @@ -170,8 +184,8 @@ export default function (props: Props) { value={currentTab} className={classes.tabBar} onChange={(_, v) => setCurrentTab(v)}> - - + + <> {currentTab === 0 && } @@ -207,7 +221,9 @@ function OrganizationDialog(props: DialogProps) { setShouldEnableAllNetworks, } = props; const [open, setOpen] = useState(false); - + const allTabs = props.getProjectTabs + ? props.getProjectTabs() + : getAllProjectTabs(); return ( @@ -263,7 +279,10 @@ function OrganizationDialog(props: DialogProps) { label={'Give this organization access to all networks'} control={ setShouldEnableAllNetworks(!shouldEnableAllNetworks) } @@ -271,6 +290,252 @@ function OrganizationDialog(props: DialogProps) { } /> {ENABLE_ALL_NETWORKS_HELPER} + {/* Editing an organization */} + {Object.entries(props.organization || {}).length > 0 && ( + <> + + }> + {allTabs.map(tab => ( + + + + + ))} + + + + + { + props.onOrganizationChange({ + ...organization, + csvCharset: target.value, + }); + }} + /> + + + {/* + Advanced Settings +
+ + setCsvCharset(evt.target.value)} + margin="normal" + /> + +
+
*/} + + + + props.onOrganizationChange({ + ...organization, + ssoSelectedType: target.value, + }) + } + input={} + /> + + {organization.ssoSelectedType === 'saml' ? ( + <> + {/* + setSSOIssuer(evt.target.value)} + margin="normal" + /> + */} + + + { + props.onOrganizationChange({ + ...organization, + ssoIssuer: target.value, + }); + }} + /> + + {/* + + setSSOEntrypoint(evt.target.value)} + margin="normal" + /> + */} + + { + props.onOrganizationChange({ + ...organization, + ssoEntrypoint: target.value, + }); + }} + /> + + + {/* + + setSSOCert(evt.target.value)} + className={classes.textField} + margin="normal" + variant="filled" + /> + */} + + + { + props.onOrganizationChange({ + ...organization, + ssoCerts: target.value, + }); + }} + /> + + + ) : null} + {organization.ssoSelectedType === 'oidc' ? ( + <> + {/* + setSSOOidcClientID(evt.target.value)} + margin="normal" + /> + */} + + + { + props.onOrganizationChange({ + ...organization, + ssoOidcClientID: target.value, + }); + }} + /> + + + {/* + + setSSOOidcClientSecret(evt.target.value) + } + margin="normal" + /> + */} + + + { + props.onOrganizationChange({ + ...organization, + ssoOidcClientSecret: target.value, + }); + }} + /> + + + {/* + + setSSOOidcConfigurationURL(evt.target.value) + } + margin="normal" + /> + */} + + + { + props.onOrganizationChange({ + ...organization, + ssoOidcConfigurationURL: target.value, + }); + }} + /> + + + ) : null} + + )}
diff --git a/fbcnms-packages/fbcnms-ui/master/OrganizationEdit.js b/fbcnms-packages/fbcnms-ui/master/OrganizationEdit.js index 861d012..d312c0a 100644 --- a/fbcnms-packages/fbcnms-ui/master/OrganizationEdit.js +++ b/fbcnms-packages/fbcnms-ui/master/OrganizationEdit.js @@ -8,32 +8,26 @@ * @format */ +import type {DataRows} from '@fbcnms/ui/components/DataGrid'; import type {Organization} from './Organizations'; -import type {SSOSelectedType} from '@fbcnms/types/auth'; -import type {Tab} from '@fbcnms/types/tabs'; +import type {WithAlert} from '@fbcnms/ui/components/Alert/withAlert'; +import ActionTable from '@fbcnms/ui/components/ActionTable'; +import ArrowBackIcon from '@material-ui/icons/ArrowBack'; import Button from '@fbcnms/ui/components/design-system/Button'; -import Checkbox from '@material-ui/core/Checkbox'; -import FormControl from '@material-ui/core/FormControl'; -import FormControlLabel from '@material-ui/core/FormControlLabel'; -import FormGroup from '@material-ui/core/FormGroup'; +// import CircularProgress from '@material-ui/core/CircularProgress'; +import DataGrid from '@fbcnms/ui/components/DataGrid'; import Grid from '@material-ui/core/Grid'; -import Input from '@material-ui/core/Input'; -import InputLabel from '@material-ui/core/InputLabel'; -import ListItemText from '@material-ui/core/ListItemText'; +import IconButton from '@material-ui/core/IconButton'; import LoadingFiller from '@fbcnms/ui/components/LoadingFiller'; -import MenuItem from '@material-ui/core/MenuItem'; -import Paper from '@material-ui/core/Paper'; +import OrganizationDialog from './OrganizationDialog'; import React from 'react'; -import SaveIcon from '@material-ui/icons/Save'; -import Select from '@material-ui/core/Select'; import Text from '@fbcnms/ui/components/design-system/Text'; -import TextField from '@material-ui/core/TextField'; -import TypedSelect from '@fbcnms/ui/components/TypedSelect'; import axios from 'axios'; -import renderList from '@fbcnms/util/renderList'; import symphony from '@fbcnms/ui/theme/symphony'; -import {getProjectTabs as getAllProjectTabs} from '@fbcnms/projects/projects'; +import withAlert from '@fbcnms/ui/components/Alert/withAlert'; + +// import {getProjectTabs as getAllProjectTabs} from '@fbcnms/projects/projects'; import {intersection} from 'lodash'; import {makeStyles} from '@material-ui/styles'; import {useAxios, useRouter} from '@fbcnms/ui/hooks'; @@ -41,10 +35,21 @@ import {useCallback, useEffect, useState} from 'react'; import {useEnqueueSnackbar} from '@fbcnms/ui/hooks/useSnackbar'; const useStyles = makeStyles(theme => ({ + arrowBack: { + paddingRight: '0px', + color: 'black', + }, + buttonText: { + color: 'black', + }, + container: { + margin: '40px 32px', + }, header: { margin: '10px', display: 'flex', justifyContent: 'space-between', + textTransform: 'capitalize', }, leftIcon: { marginRight: theme.spacing(), @@ -69,33 +74,70 @@ const useStyles = makeStyles(theme => ({ flexGrow: { flexGrow: 1, }, + loading: { + textAlign: 'center', + }, + titleRow: { + margin: '16px 0', + }, })); -type Props = { - getProjectTabs?: () => Array<{id: Tab, name: string}>, +// type Props = { +// getProjectTabs?: () => Array<{id: Tab, name: string}>, +// }; + +export type EditUser = { + id: string, + email: string, + role: number, + networkIDs?: string[], + organization?: string, +}; +type TitleRowProps = { + title: string, + buttonTitle: string, + onClick: () => void, }; +function TitleRow(props: TitleRowProps) { + const classes = useStyles(); + return ( + + {props.title} + + + ); +} -export default function OrganizationEdit(props: Props) { - const {match} = useRouter(); +export default function OrganizationEdit() { + const {match, history, relativeUrl} = useRouter(); + const [addUser, setAddUser] = useState(false); const classes = useStyles(); const enqueueSnackbar = useEnqueueSnackbar(); const [name, setName] = useState(''); - const [csvCharset, setCsvCharset] = useState(''); - const [tabs, setTabs] = useState>(new Set()); + // const [csvCharset, setCsvCharset] = useState(''); + // const [tabs, setTabs] = useState>(new Set()); const [shouldEnableAllNetworks, setShouldEnableAllNetworks] = useState(false); + const [networkIds, setNetworkIds] = useState>(new Set()); - const [ssoSelectedType, setSSOSelectedType] = useState( - 'saml', - ); - const [ssoIssuer, setSSOIssuer] = useState(''); - const [ssoEntrypoint, setSSOEntrypoint] = useState(''); - const [ssoCert, setSSOCert] = useState(''); - const [ssoOidcClientID, setSSOOidcClientID] = useState(''); - const [ssoOidcClientSecret, setSSOOidcClientSecret] = useState(''); + // const [ssoSelectedType, setSSOSelectedType] = useState( + // 'saml', + // ); + // const [ssoIssuer, setSSOIssuer] = useState(''); + // const [ssoEntrypoint, setSSOEntrypoint] = useState(''); + // const [ssoCert, setSSOCert] = useState(''); + const [_ssoOidcClientID, setSSOOidcClientID] = useState(''); + const [_ssoOidcClientSecret, setSSOOidcClientSecret] = useState(''); const [ - ssoOidcConfigurationURL, + _ssoOidcConfigurationURL, setSSOOidcConfigurationURL, ] = useState(''); + const [dialog, setDialog] = useState(false); + const [createError, setCreateError] = useState(''); + const [refresh, setRefresh] = useState(Date.now()); const orgRequest = useAxios({ method: 'get', @@ -103,13 +145,13 @@ export default function OrganizationEdit(props: Props) { onResponse: useCallback(res => { const {organization} = res.data; setName(organization.name); - setTabs(new Set(organization.tabs)); + // setTabs(new Set(organization.tabs)); setNetworkIds(new Set(organization.networkIDs)); - setCsvCharset(organization.csvCharset); - setSSOSelectedType(organization.ssoSelectedType); - setSSOIssuer(organization.ssoIssuer); - setSSOEntrypoint(organization.ssoEntrypoint); - setSSOCert(organization.ssoCert); + // setCsvCharset(organization.csvCharset); + // setSSOSelectedType(organization.ssoSelectedType); + // setSSOIssuer(organization.ssoIssuer); + // setSSOEntrypoint(organization.ssoEntrypoint); + // setSSOCert(organization.ssoCert); setSSOOidcClientID(organization.ssoOidcClientID); setSSOOidcClientSecret(organization.ssoOidcClientSecret); setSSOOidcConfigurationURL(organization.ssoOidcConfigurationURL); @@ -145,22 +187,26 @@ export default function OrganizationEdit(props: Props) { ? [] : networksRequest.response.data.sort(); + // const allTabs = props.getProjectTabs + // ? props.getProjectTabs() + // : getAllProjectTabs(); + const onSave = () => { axios .put('/master/organization/async/' + match.params.name, { - name, - tabs: Array.from(tabs), + name: organization.name, + // tabs: Array.from(tabs), networkIDs: shouldEnableAllNetworks ? allNetworks - : Array.from(networkIds), - csvCharset, - ssoSelectedType, - ssoIssuer, - ssoEntrypoint, - ssoCert, - ssoOidcClientID, - ssoOidcClientSecret, - ssoOidcConfigurationURL, + : Array.from(organization.networkIDs), + // csvCharset, + // ssoSelectedType, + // ssoIssuer, + // ssoEntrypoint, + // ssoCert, + // ssoOidcClientID, + // ssoOidcClientSecret, + // ssoOidcConfigurationURL, }) .then(_res => { enqueueSnackbar('Updated organization successfully', { @@ -175,205 +221,279 @@ export default function OrganizationEdit(props: Props) { }); }; - const allTabs = props.getProjectTabs - ? props.getProjectTabs() - : getAllProjectTabs(); - return ( - - - - Organization: {organization.name} - - - - - - Basic Info -
- - setName(evt.target.value)} - margin="normal" - /> - - - - Accessible Tabs - }> - {allTabs.map(tab => ( - - - - - ))} - - - - - - setShouldEnableAllNetworks(target.checked) - } - color="primary" - margin="normal" - /> - } - label="Enable All Networks" - /> - {!shouldEnableAllNetworks && ( - - - Accessible Networks - - }> - {allNetworks.map(network => ( - - - - - ))} - - - )} - -
-
- - Advanced Settings -
- - setCsvCharset(evt.target.value)} - margin="normal" - /> - -
-
- - Single Sign-On -
- - setSSOSelectedType(value)} - input={} - /> - - {ssoSelectedType === 'saml' ? ( - <> - - setSSOIssuer(evt.target.value)} - margin="normal" - /> - - - setSSOEntrypoint(evt.target.value)} - margin="normal" - /> - - - setSSOCert(evt.target.value)} - className={classes.textField} - margin="normal" - variant="filled" - /> - - - ) : null} - {ssoSelectedType === 'oidc' ? ( - <> - - setSSOOidcClientID(evt.target.value)} - margin="normal" - /> - - - setSSOOidcClientSecret(evt.target.value)} - margin="normal" - /> - - - - setSSOOidcConfigurationURL(evt.target.value) - } - margin="normal" - /> - - - ) : null} -
-
- -
- -
-
-
-
+ /> + +
+ + + { + setAddUser(true); + setDialog(true); + }} + /> + setRefresh(Date.now())} + /> + + + + + + + ); +} + +type OverviewProps = { + name: string, + networkIds: Set, +}; +export function OrganizationSummary(props: OverviewProps) { + const {name, networkIds} = props; + const kpiData: DataRows[] = [ + [ + { + category: 'Organization Name', + value: name, + }, + ], + [ + { + category: 'Accessible Networks', + value: [...(networkIds || [])].join(', ') || '-', + }, + ], + [ + { + category: 'Link to Organization Portal', + value: `link to ${name} org`, + }, + ], + ]; + return ; +} +function OrganizationUsersTable( + props: WithAlert & {refresh: Date, setRefresh: Date => void}, +) { + const tableRef = React.createRef(); + // const [refresh, setRefresh] = useState(Date.now()) + // refresh data on subscriber add + useEffect(() => { + tableRef.current?.onQueryChange(); + }, [props.refresh]); + const onDelete = user => { + props + .confirm({ + message: ( + + Are you sure you want to delete the user{' '} + {user.email}? + + ), + confirmLabel: 'Delete', + }) + .then(confirmed => { + if (confirmed) { + axios + .delete('/user/async/' + user.id) + .then(() => { + props.setRefresh(); + }) + .catch(e => console.log(e)); + } + }); + }; + + type UserRowType = { + email: string, + role: number, + id: string, + }; + + const menuItems = [ + { + name: 'View', + handleFunc: () => { + // history.push(relativeUrl(`/detail/${currRow.name}`)); + }, + }, + { + name: 'Remove', + handleFunc: () => { + onDelete(currRow); + }, + }, + ]; + + const columnStruct = [ + { + title: '', + field: '', + width: '40px', + render: rowData => ( + {rowData.tableData?.id + 1} + ), + }, + { + title: 'Email', + field: 'email', + }, + { + title: 'Role', + field: 'role', + }, + ]; + const [currRow, setCurrRow] = useState({}); + // const classes = useStyles(); + const {match} = useRouter(); + + return ( + <> + + new Promise((resolve, _reject) => { + // let url = 'https://reqres.in/api/users?' + // url += 'per_page=' + query.pageSize + // url += '&page=' + (query.page + 1) + // fetch(url) + axios + .get(`/master/organization/async/${match.params.name}/users`) + // .then(response => console.log(response)) + .then(result => { + const users: Array = result.data.map(user => { + return { + email: user.email, + role: user.role, + id: user.id, + }; + }); + resolve({ + data: users, + }); + }); + }) + } + // data={tableData || []} + columns={columnStruct} + handleCurrRow={(row: UserRowType) => { + setCurrRow(row); + }} + menuItems={menuItems} + localization={{ + // hide 'Actions' in table header + header: {actions: ''}, + }} + options={{ + actionsColumnIndex: -1, + sorting: true, + // hide table title and toolbar + toolbar: false, + // pageSizeOptions: [10, 20], + paging: false, + }} + /> + ); } + +const UserTable = withAlert(OrganizationUsersTable); diff --git a/fbcnms-packages/fbcnms-ui/master/Organizations.js b/fbcnms-packages/fbcnms-ui/master/Organizations.js index 5433ca9..b61ca73 100644 --- a/fbcnms-packages/fbcnms-ui/master/Organizations.js +++ b/fbcnms-packages/fbcnms-ui/master/Organizations.js @@ -291,6 +291,8 @@ function Organizations(props: Props) { path={relativePath('/new')} render={() => ( setAddUser(true)} onClose={() => {