diff --git a/frontend/src/Editor/Header/RightTopHeaderButtons/ManageAppUsers.jsx b/frontend/src/Editor/Header/RightTopHeaderButtons/ManageAppUsers.jsx index 45f05b4af1..a8d34774cb 100644 --- a/frontend/src/Editor/Header/RightTopHeaderButtons/ManageAppUsers.jsx +++ b/frontend/src/Editor/Header/RightTopHeaderButtons/ManageAppUsers.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { appService, appsService, authenticationService } from '@/_services'; import Modal from 'react-bootstrap/Modal'; import { toast } from 'react-hot-toast'; @@ -15,6 +15,7 @@ import { ToolTip } from '@/_components/ToolTip'; import { TOOLTIP_MESSAGES } from '@/_helpers/constants'; import { useAppDataStore } from '@/_stores/appDataStore'; import { retrieveWhiteLabelText } from '@white-label/whiteLabelling'; +import InfoIcon from '@assets/images/icons/info.svg'; class ManageAppUsersComponent extends React.Component { constructor(props) { @@ -32,6 +33,7 @@ class ManageAppUsersComponent extends React.Component { value: null, error: '', }, + isHovered: false, isSlugUpdated: false, }; } @@ -85,7 +87,6 @@ class ManageAppUsersComponent extends React.Component { toast.error(error); }); }; - toggleAppVisibility = () => { const newState = !this.props.isPublic; this.setState({ @@ -170,7 +171,13 @@ class ManageAppUsersComponent extends React.Component { }); } }; + handleMouseEnter = () => { + this.setState({ isHovered: true }); + }; + handleMouseLeave = () => { + this.setState({ isHovered: false }); + }; render() { const { appId, isSlugVerificationInProgress, newSlug, isSlugUpdated } = this.state; @@ -178,66 +185,104 @@ class ManageAppUsersComponent extends React.Component { const shareableLink = appLink + (this.props.slug || appId); const slugButtonClass = !_.isEmpty(newSlug.error) ? 'is-invalid' : 'is-valid'; const embeddableLink = ``; - const shouldWeDisableShareModal = !this.props.isVersionReleased; + const { isHovered } = this.state.isHovered; return ( - -
+ { + this.validateThePreExistingSlugs(); + this.setState({ showModal: true }); + }} > { - this.validateThePreExistingSlugs(); - !shouldWeDisableShareModal && this.setState({ showModal: true }); - }} > - - - {this.props.t('editor.share', 'Share')} - - - - - - { - ); } } diff --git a/frontend/src/GlobalDatasources/GlobalDataSourcesPage/index.jsx b/frontend/src/GlobalDatasources/GlobalDataSourcesPage/index.jsx index 0512eadf28..1fddbb5b96 100644 --- a/frontend/src/GlobalDatasources/GlobalDataSourcesPage/index.jsx +++ b/frontend/src/GlobalDatasources/GlobalDataSourcesPage/index.jsx @@ -31,7 +31,6 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour const [addingDataSource, setAddingDataSource] = useState(false); const [suggestingDataSource, setSuggestingDataSource] = useState(false); const { t } = useTranslation(); - const navigate = useNavigate(); const { admin } = authenticationService.currentSessionValue; const marketplaceEnabled = admin && window.public_config?.ENABLE_MARKETPLACE_FEATURE == 'true'; const [modalProps, setModalProps] = useState({ @@ -296,34 +295,6 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour }; const renderCardGroup = (source, type) => { - if (type === 'Plugins' && source.length === 0) { - return ( -
-
- -
-
No plugins added
- {admin && ( - <> -
- Browse through plugins in marketplace to add them as a Data Source.{' '} -
- { - marketplaceEnabled - ? navigate('/integrations') - : toast.error('Please enable marketplace to add plugins'); - }} - style={{ margin: 'auto' }} - variant="secondary" - > - Add plugins - - - )} -
- ); - } const addDataSourceBtn = (item) => ( ))} + {type === 'Plugins' && ( +
+
{ + if (marketplaceEnabled) { + window.open('/integrations', '_blank'); + } else { + toast.error('Please enable marketplace to add plugins'); + } + }} + data-cy={`data-source-add-plugin`} + > +
+
+ +

+ Add plugin +
+
+
+
+ )}
); diff --git a/frontend/src/OrganizationSettingsPage/constant.js b/frontend/src/OrganizationSettingsPage/constant.js new file mode 100644 index 0000000000..4a5153a41a --- /dev/null +++ b/frontend/src/OrganizationSettingsPage/constant.js @@ -0,0 +1,6 @@ +export const workspaceSettingsLinks = [ + { id: 'users', name: 'Users', route: 'users', conditions: ['admin'] }, + { id: 'groups', name: 'Groups', route: 'groups', conditions: ['admin'] }, + { id: 'workspacelogin', name: 'Workspace login', route: 'workspace-login', conditions: ['admin'] }, + { id: 'workspacevariables', name: 'Workspace variables', route: 'workspace-variables', conditions: ['admin'] }, +]; diff --git a/frontend/src/OrganizationSettingsPage/index.jsx b/frontend/src/OrganizationSettingsPage/index.jsx index 32094dbee4..b79cdad83d 100644 --- a/frontend/src/OrganizationSettingsPage/index.jsx +++ b/frontend/src/OrganizationSettingsPage/index.jsx @@ -1,57 +1,59 @@ import React, { useEffect, useState, useContext } from 'react'; import cx from 'classnames'; -import { useParams, Outlet, Link, useNavigate, useLocation } from 'react-router-dom'; +import { Outlet, Link, useNavigate, useLocation } from 'react-router-dom'; import Layout from '@/_ui/Layout'; import { authenticationService } from '@/_services'; import { BreadCrumbContext } from '../App/App'; import FolderList from '@/_ui/FolderList/FolderList'; import { OrganizationList } from '../_components/OrganizationManager/List'; -import { getWorkspaceId } from '@/_helpers/utils'; -import { getSubpath } from '@/_helpers/routes'; +import { workspaceSettingsLinks } from './constant'; export function OrganizationSettings(props) { - const [admin, setAdmin] = useState(authenticationService.currentSessionValue?.admin); - const [selectedTab, setSelectedTab] = useState(admin ? 'Users & permissions' : 'manageEnvVars'); + const admin = authenticationService.currentSessionValue?.admin; + const [selectedTab, setSelectedTab] = useState(admin ? workspaceSettingsLinks[0].id : 'workspacevariables'); const navigate = useNavigate(); const location = useLocation(); const { updateSidebarNAV } = useContext(BreadCrumbContext); - const { workspaceId } = useParams(); + const [conditionObj, setConditionObj] = useState({ admin: authenticationService.currentSessionValue?.admin }); - const sideBarNavs = ['Users', 'Groups', 'Workspace login', 'Workspace variables']; - const defaultOrgName = (groupName) => { - switch (groupName) { - case 'users': - return 'Users'; - case 'groups': - return 'Groups'; - case 'workspace-login': - return 'Workspace login'; - case 'workspace-variables': - return 'Workspace variables'; - default: - return groupName; + const checkConditions = (conditions, conditionsObj) => { + if (!conditions || conditions.length === 0) { + return true; } + return conditions.every((condition) => conditionsObj?.[condition] === true); + }; + + //Filtered Links from the workspace settings links array + const filteredLinks = () => + workspaceSettingsLinks.filter((item) => { + return checkConditions(item.conditions, conditionObj); + }); + + const getMenuFromRoute = (route) => { + return workspaceSettingsLinks?.find((e) => e.route === route) || {}; }; useEffect(() => { const subscription = authenticationService.currentSession.subscribe((newOrd) => { - setAdmin(newOrd?.admin); + setConditionObj({ admin: newOrd?.admin }); }); - admin ? updateSidebarNAV('Users') : updateSidebarNAV('Workspace variables'); - - () => subscription.unsubsciption(); const selectedTabFromRoute = location.pathname.split('/').pop(); if (selectedTabFromRoute === 'workspace-settings') { - setSelectedTab(admin ? 'Users' : 'Workspace variables'); - const subPath = getSubpath(); - const path = subPath ? `${subPath}/${workspaceId}/workspace-settings` : `/${workspaceId}/workspace-settings`; - window.location.href = admin ? `${path}/users` : `${path}/workspace-variables`; + // No Sub routes added loading first one + setSelectedTab(admin ? workspaceSettingsLinks[0].id : 'workspacevariables'); } else { - setSelectedTab(defaultOrgName(selectedTabFromRoute)); + setSelectedTab(getMenuFromRoute(selectedTabFromRoute)?.id); } - updateSidebarNAV(defaultOrgName(selectedTabFromRoute)); - }, [navigate, workspaceId, authenticationService.currentSessionValue?.admin]); + + return () => subscription.unsubscribe(); + }, [authenticationService.currentSessionValue?.admin]); + + useEffect(() => { + const menu = workspaceSettingsLinks?.find((m) => m.id === selectedTab); + updateSidebarNAV(menu?.name || ''); + navigate(menu?.route || ''); + }, [selectedTab]); return ( @@ -59,12 +61,11 @@ export function OrganizationSettings(props) {
- {sideBarNavs.map((item, index) => { + {filteredLinks().map((item, index) => { const Wrapper = ({ children }) => <>{children}; return ( - {admin && ( - { - setSelectedTab(defaultOrgName(item)); - if (item == 'Users') updateSidebarNAV('Users'); - else updateSidebarNAV(item); - }} - selectedItem={selectedTab == defaultOrgName(item)} - renderBadgeForItems={['Workspace constants']} - renderBadge={() => ( - - new - - )} - dataCy={item.toLowerCase().replace(/\s+/g, '-')} - > - {item} - - )} + { + setSelectedTab(item.id); + }} + selectedItem={selectedTab == item.id} + renderBadgeForItems={[]} + renderBadge={() => ( + + new + + )} + dataCy={item.name.toLowerCase().replace(/\s+/g, '-')} + > + {item.name} + ); diff --git a/frontend/src/_components/OrganizationManager/CreateOrganization.jsx b/frontend/src/_components/OrganizationManager/CreateOrganization.jsx index 0b777db87e..6357b000bb 100644 --- a/frontend/src/_components/OrganizationManager/CreateOrganization.jsx +++ b/frontend/src/_components/OrganizationManager/CreateOrganization.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { organizationService } from '@/_services'; import AlertDialog from '@/_ui/AlertDialog'; import { useTranslation } from 'react-i18next'; @@ -18,10 +18,11 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { const [isSlugDisabled, setSlugDisabled] = useState(true); const darkMode = localStorage.getItem('darkMode') === 'true'; const { t } = useTranslation(); + const isSlugSet = useRef(false); // Flag to track if slug has been initially set + const sluginput = useRef(''); const createOrganization = () => { let emptyError = false; - [name, slug].map((field, index) => { if (!field?.value?.trim()) { index === 0 @@ -77,7 +78,6 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { !(field === 'slug'), field === 'slug' ); - /* If the basic validation is passing. then check the uniqueness */ if (error?.status === true) { try { @@ -92,7 +92,6 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { }; } } - const disabled = !error?.status; const updatedValue = { value, @@ -126,17 +125,47 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { setShowCreateOrg(false); setNameDisabled(true); setSlugDisabled(true); + isSlugSet.current = false; }; const delayedSlugChange = _.debounce(async (value) => { setSlugProgress(true); await handleInputChange(value, 'slug'); }, 300); - const delayedNameChange = _.debounce(async (value) => { setWorkspaceNameProgress(true); await handleInputChange(value, 'name'); }, 300); + useEffect(() => { + if (!isSlugSet.current && name.value && !slugProgress && !workspaceNameProgress) { + const defaultValue = + name?.value + .replace(/\s+/g, '') + .toLowerCase() + .replace(/[^a-z0-9-\s]/g, '') || ''; + setSlug({ value: defaultValue, error: '' }); + + const checkWorkspaceUniqueness = async () => { + try { + await organizationService.checkWorkspaceUniqueness(null, defaultValue); + sluginput.current.value = defaultValue; + } catch (errResponse) { + let error = { + status: false, + errorMsg: errResponse?.error, + }; + setSlug({ value: defaultValue, error: error?.errorMsg }); + } + }; + checkWorkspaceUniqueness(); + setSlugDisabled(false); + setSlugProgress(false); + } + if (slugProgress && !isSlugSet.current) { + // this is to denote that the user has tried editing the slug -- so now slug and name are independent of each other + isSlugSet.current = true; + } + }, [name.value, slug.value, slugProgress, workspaceNameProgress, isSlugSet]); const isDisabled = isCreating || isNameDisabled || isSlugDisabled || slugProgress || workspaceNameProgress; @@ -168,6 +197,8 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { + ) : name.value && !workspaceNameProgress ? ( + ) : (
)} + {slug?.error ? (