Merge pull request #10626 from ToolJet/release/platformv19.2

Release Platform v19.2
This commit is contained in:
Midhun G S 2024-09-18 20:56:38 +05:30 committed by GitHub
commit b05fc6ed00
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 420 additions and 263 deletions

View file

@ -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 = `<iframe width="560" height="315" src="${appLink}${this.props.slug}" title="${this.whiteLabelText} app - ${this.props.slug}" frameborder="0" allowfullscreen></iframe>`;
const shouldWeDisableShareModal = !this.props.isVersionReleased;
const { isHovered } = this.state.isHovered;
return (
<ToolTip
message={TOOLTIP_MESSAGES.SHARE_URL_UNAVAILABLE}
placement={!this.props.isVersionReleased ? 'bottom' : 'left'}
show={shouldWeDisableShareModal}
>
<div
title={!shouldWeDisableShareModal ? 'Share' : ''}
className="manage-app-users editor-header-icon tj-secondary-btn"
data-cy="share-button-link"
<div title={'Share'} className="manage-app-users" data-cy="share-button-link">
<span
className="manage-app-users tj-secondary-btn editor-header-icon cursor-pointer"
onClick={() => {
this.validateThePreExistingSlugs();
this.setState({ showModal: true });
}}
>
<span
className={cx('d-flex', {
'share-disabled': shouldWeDisableShareModal,
'share-disabled': false,
})}
onClick={() => {
this.validateThePreExistingSlugs();
!shouldWeDisableShareModal && this.setState({ showModal: true });
}}
>
<SolidIcon name="share" width="14" className="cursor-pointer" fill="#3E63DD" />
</span>
<Modal
show={this.state.showModal}
size="lg"
backdrop="static"
centered={true}
keyboard={true}
animation={false}
onEscapeKeyDown={this.hideModal}
className={`app-sharing-modal animation-fade ${this.props.darkMode ? 'dark-theme' : ''}`}
contentClassName={this.props.darkMode ? 'dark-theme' : ''}
>
<Modal.Header>
<Modal.Title data-cy="modal-header">{this.props.t('editor.share', 'Share')}</Modal.Title>
<span onClick={this.hideModal} data-cy="modal-close-button">
<SolidIcon name="remove" className="cursor-pointer" aria-label="Close" />
</span>
</Modal.Header>
<Modal.Body>
{
<div class="shareable-link-container">
<div className="make-public mb-3">
<div className="form-check form-switch d-flex align-items-center">
<input
className="form-check-input"
type="checkbox"
onClick={this.toggleAppVisibility}
checked={this?.props?.isPublic}
disabled={this.state.ischangingVisibility}
data-cy="make-public-app-toggle"
/>
<span className="form-check-label field-name" data-cy="make-public-app-label">
{this.props.t('editor.shareModal.makeApplicationPublic', 'Make application public')}
</span>
</div>
</div>
</span>
<Modal
show={this.state.showModal}
size="lg"
backdrop="static"
centered={true}
keyboard={true}
animation={false}
onEscapeKeyDown={this.hideModal}
className={`app-sharing-modal animation-fade ${this.props.darkMode ? 'dark-theme' : ''}`}
contentClassName={this.props.darkMode ? 'dark-theme' : ''}
>
<Modal.Header>
<Modal.Title data-cy="modal-header">{this.props.t('editor.share', 'Share')}</Modal.Title>
<span onClick={this.hideModal} data-cy="modal-close-button">
<SolidIcon name="remove" className="cursor-pointer" aria-label="Close" />
</span>
</Modal.Header>
<Modal.Body>
{
<div class="shareable-link-container">
<div className="make-public mb-3">
<div className="form-check form-switch d-flex align-items-center">
{this.props.isVersionReleased ? (
<div>
<input
className="form-check-input"
type="checkbox"
onClick={this.toggleAppVisibility}
checked={this?.props?.isPublic}
disabled={this.state.ischangingVisibility}
data-cy="make-public-app-toggle"
/>
<span className="form-check-label field-name" data-cy="make-public-app-label">
{this.props.t('editor.shareModal.makeApplicationPublic', 'Make application public')}
</span>
</div>
) : (
<div style={{ display: 'flex', alignItems: 'left', gap: '8px' }}>
<ToolTip
message={TOOLTIP_MESSAGES.RELEASE_VERSION_URL_UNAVAILABLE}
placement={'top'}
show={isHovered}
>
<div
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
style={{
width: '32px',
height: '18px',
marginLeft: '-40px',
}}
>
<input
className="form-check-input"
type="checkbox"
disabled
style={{
opacity: 0.3,
cursor: 'default',
margin: 0,
padding: 0,
}}
/>
</div>
</ToolTip>
<span
className="form-check-label field-name"
data-cy="make-public-app-label"
style={{ opacity: 0.6 }}
>
{this.props.t('editor.shareModal.makeApplicationPublic', 'Make application public')}
</span>
</div>
)}
</div>
</div>
{this.props.isVersionReleased ? (
<div className="shareable-link tj-app-input mb-2">
<label data-cy="shareable-app-link-label" className="field-name">
{this.props.t('editor.shareModal.shareableLink', 'Shareable app link')}
@ -259,6 +304,7 @@ class ManageAppUsersComponent extends React.Component {
style={{ maxWidth: '150px' }}
defaultValue={this.props.slug}
data-cy="app-name-slug-input"
disabled={!this.props.isVersionReleased}
/>
{isSlugVerificationInProgress && (
<div className="icon-container">
@ -344,60 +390,69 @@ class ManageAppUsersComponent extends React.Component {
>{`URL-friendly 'slug' consists of lowercase letters, numbers, and hyphens`}</label>
)}
</div>
{(this?.props?.isPublic || window?.public_config?.ENABLE_PRIVATE_APP_EMBED === 'true') && (
<div className="tj-app-input">
<label className="field-name" data-cy="iframe-link-label">
Embedded app link
</label>
<span className={`tj-text-input justify-content-between ${this.props.darkMode ? 'dark' : ''}`}>
<span data-cy="iframe-link">{embeddableLink}</span>
<span className="copy-container">
<CopyToClipboard
text={embeddableLink}
onCopy={() => toast.success('Link copied to clipboard')}
>
<svg
className="cursor-pointer"
width="17"
height="18"
viewBox="0 0 17 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
data-cy="iframe-link-copy-button"
>
<path
d="M9.11154 5.18031H5.88668V4.83302C5.88668 3.29859 7.13059 2.05469 8.66502 2.05469H12.8325C14.3669 2.05469 15.6109 3.29859 15.6109 4.83302V9.00052C15.6109 10.535 14.3669 11.7789 12.8325 11.7789H12.4852V8.554C12.4852 6.69076 10.9748 5.18031 9.11154 5.18031Z"
fill="#889096"
/>
<path
d="M8.66502 15.9464H4.49752C2.96309 15.9464 1.71918 14.7025 1.71918 13.168V9.00052C1.71918 7.46609 2.96309 6.22219 4.49752 6.22219H8.66502C10.1994 6.22219 11.4434 7.46609 11.4434 9.00052V13.168C11.4434 14.7025 10.1994 15.9464 8.66502 15.9464Z"
fill="#889096"
/>
</svg>
</CopyToClipboard>
</span>
</span>
) : (
<div className="shareable-link tj-app-input mb-2">
<label data-cy="shareable-app-link-label" className="field-name">
{this.props.t('editor.shareModal.shareableLink', 'Shareable app link')}
</label>
<div className="empty-version">
<InfoIcon style={{ width: '12px', marginRight: '5px' }} />
<span>This version has not been released yet</span>
</div>
)}
</div>
}
</Modal.Body>
</div>
)}
<Modal.Footer className="manage-app-users-footer">
{this.isUserAdmin && (
<Link
to={getPrivateRoute('workspace_settings')}
target="_blank"
className={`btn border-0 default-secondary-button float-right1`}
data-cy="manage-users-button"
>
Manage users
</Link>
)}
</Modal.Footer>
</Modal>
</div>
</ToolTip>
{((this?.props?.isVersionReleased && this?.props?.isPublic) ||
window?.public_config?.ENABLE_PRIVATE_APP_EMBED === 'true') && (
<div className="tj-app-input">
<label className="field-name" data-cy="iframe-link-label">
Embedded app link
</label>
<span className={`tj-text-input justify-content-between ${this.props.darkMode ? 'dark' : ''}`}>
<span data-cy="iframe-link">{embeddableLink}</span>
<span className="copy-container">
<CopyToClipboard text={embeddableLink} onCopy={() => toast.success('Link copied to clipboard')}>
<svg
className="cursor-pointer"
width="17"
height="18"
viewBox="0 0 17 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
data-cy="iframe-link-copy-button"
>
<path
d="M9.11154 5.18031H5.88668V4.83302C5.88668 3.29859 7.13059 2.05469 8.66502 2.05469H12.8325C14.3669 2.05469 15.6109 3.29859 15.6109 4.83302V9.00052C15.6109 10.535 14.3669 11.7789 12.8325 11.7789H12.4852V8.554C12.4852 6.69076 10.9748 5.18031 9.11154 5.18031Z"
fill="#889096"
/>
<path
d="M8.66502 15.9464H4.49752C2.96309 15.9464 1.71918 14.7025 1.71918 13.168V9.00052C1.71918 7.46609 2.96309 6.22219 4.49752 6.22219H8.66502C10.1994 6.22219 11.4434 7.46609 11.4434 9.00052V13.168C11.4434 14.7025 10.1994 15.9464 8.66502 15.9464Z"
fill="#889096"
/>
</svg>
</CopyToClipboard>
</span>
</span>
</div>
)}
</div>
}
</Modal.Body>
<Modal.Footer className="manage-app-users-footer">
{this.isUserAdmin && (
<Link
to={getPrivateRoute('workspace_settings')}
target="_blank"
className={`btn border-0 default-secondary-button float-right1`}
data-cy="manage-users-button"
>
Manage users
</Link>
)}
</Modal.Footer>
</Modal>
</div>
);
}
}

View file

@ -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 (
<div className="add-plugins-container">
<div className="warning-container mb-2">
<SolidIcon name="warning" />
</div>
<div className="tj-text-sm font-weight-500 tj-text">No plugins added</div>
{admin && (
<>
<div className="tj-text-xsm font-weight-400 mt-2 mb-3">
Browse through plugins in marketplace to add them as a Data Source.{' '}
</div>
<ButtonSolid
onClick={() => {
marketplaceEnabled
? navigate('/integrations')
: toast.error('Please enable marketplace to add plugins');
}}
style={{ margin: 'auto' }}
variant="secondary"
>
Add plugins
</ButtonSolid>
</>
)}
</div>
);
}
const addDataSourceBtn = (item) => (
<ButtonSolid
disabled={addingDataSource}
@ -366,6 +337,36 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
titleClassName={'datasource-card-title'}
/>
))}
{type === 'Plugins' && (
<div style={{ height: '122px', width: '164px' }} className={`col-md-2 mb-4 `}>
<div
className="card add-plugin-card"
role="button"
onClick={() => {
if (marketplaceEnabled) {
window.open('/integrations', '_blank');
} else {
toast.error('Please enable marketplace to add plugins');
}
}}
data-cy={`data-source-add-plugin`}
>
<div className="card-body">
<center style={{ paddingTop: '5px' }}>
<SolidIcon
name="plus"
fill={'var(--icon-default)'}
width={35}
height={35}
style={{ marginBottom: '8px' }}
/>
<br></br>
<span className={`datasource-card-title add-plugin-card-title`}>Add plugin</span>
</center>
</div>
</div>
</div>
)}
</div>
</>
);

View file

@ -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'] },
];

View file

@ -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 (
<Layout switchDarkMode={props.switchDarkMode} darkMode={props.darkMode}>
@ -59,12 +61,11 @@ export function OrganizationSettings(props) {
<div className="row gx-0">
<div className="organization-page-sidebar col ">
<div className="workspace-nav-list-wrap">
{sideBarNavs.map((item, index) => {
{filteredLinks().map((item, index) => {
const Wrapper = ({ children }) => <>{children}</>;
return (
<Wrapper key={index}>
<Link
to={`/${workspaceId}/workspace-settings/${item.toLowerCase().replace(/\s+/g, '-')}`} // Update the URL path here
key={index}
style={{
textDecoration: 'none',
@ -74,30 +75,26 @@ export function OrganizationSettings(props) {
backgroundColor: 'inherit',
}}
>
{admin && (
<FolderList
className="workspace-settings-nav-items"
key={index}
onClick={() => {
setSelectedTab(defaultOrgName(item));
if (item == 'Users') updateSidebarNAV('Users');
else updateSidebarNAV(item);
}}
selectedItem={selectedTab == defaultOrgName(item)}
renderBadgeForItems={['Workspace constants']}
renderBadge={() => (
<span
style={{ width: '40px', textTransform: 'lowercase' }}
className="badge bg-color-primary badge-pill"
>
new
</span>
)}
dataCy={item.toLowerCase().replace(/\s+/g, '-')}
>
{item}
</FolderList>
)}
<FolderList
className="workspace-settings-nav-items"
key={index}
onClick={() => {
setSelectedTab(item.id);
}}
selectedItem={selectedTab == item.id}
renderBadgeForItems={[]}
renderBadge={() => (
<span
style={{ width: '40px', textTransform: 'lowercase' }}
className="badge bg-color-primary badge-pill"
>
new
</span>
)}
dataCy={item.name.toLowerCase().replace(/\s+/g, '-')}
>
{item.name}
</FolderList>
</Link>
</Wrapper>
);

View file

@ -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 }) => {
<label className="label tj-input-error" data-cy="workspace-error-label">
{name?.error || ''}
</label>
) : name.value && !workspaceNameProgress ? (
<label className="label label-success" data-cy="slug-sucess-label">{`Workspace name accepted!`}</label>
) : (
<label className="label label-info" data-cy="workspace-name-info-label">
Name must be unique and max 50 characters
@ -183,6 +214,7 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
className={`form-control ${slug?.error ? 'is-invalid' : 'is-valid'}`}
placeholder={t('header.organization.workspaceSlug', 'Unique workspace slug')}
disabled={isCreating}
ref={sluginput}
maxLength={50}
onChange={async (e) => {
e.persist();
@ -203,6 +235,7 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
</svg>
</div>
)}
{slug?.error ? (
<label className="label tj-input-error" data-cy="input-label-error">
{slug?.error || ''}

View file

@ -91,6 +91,7 @@ export const ERROR_MESSAGES = {
export const TOOLTIP_MESSAGES = {
SHARE_URL_UNAVAILABLE: 'Share URL is unavailable until current version is released',
RELEASE_VERSION_URL_UNAVAILABLE: 'Release the version to make it public',
};
export const DATA_SOURCE_TYPE = {

View file

@ -1,5 +1,5 @@
export const APP_ERROR_TYPE = {
IMPORT_EXPORT_SERVICE: {
UNSUPPORTED_VERSION_ERROR: "Can't import higher version application to lower version",
UNSUPPORTED_VERSION_ERROR: 'Apps built on later versions of ToolJet cannot be imported',
},
};

View file

@ -7,13 +7,15 @@ import { useEditorStore } from '@/_stores/editorStore';
import { useQueryPanelStore } from '@/_stores/queryPanelStore';
import update from 'immutability-helper';
const { diff } = require('deep-object-diff');
import { useAppDataStore } from './appDataStore';
import { authenticationService } from '@/_services';
const initialState = {
queries: {},
components: {},
globals: {
theme: { name: 'light' },
urlparams: null,
currentUser: {},
},
errors: {},
variables: {},
@ -53,9 +55,22 @@ export const useCurrentStateStore = create(
},
setEditorReady: (isEditorReady) => set({ isEditorReady }),
initializeCurrentStateOnVersionSwitch: () => {
//fetch user for current app
const currentUser = useAppDataStore.getState().currentUser;
const userVars = {
email: currentUser?.email,
firstName: currentUser?.first_name,
lastName: currentUser?.last_name,
groups: authenticationService.currentSessionValue.group_permissions?.map((group) => group.group),
ssoUserInfo: currentUser?.sso_user_info,
};
const newInitialState = {
...initialState,
constants: get().constants,
globals: {
...get().globals,
currentUser: userVars,
},
};
set({ ...newInitialState }, false, {
type: 'INITIALIZE_CURRENT_STATE_ON_VERSION_SWITCH',

View file

@ -300,4 +300,37 @@
border-radius: 6px;
margin: auto;
}
}
.add-plugin-card{
border: 1px dashed var(--border-default, #CCD1D5);
background-color: var(--interactive-weak);
&:hover{
border: 1px dashed var(--border-strong, #CCD1D5);
background-color: var(--interactive-default);
}
}
.add-plugin-card-title{
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: var(--text-default);
}
.dark-theme{
.add-plugin-card{
border: 1px dashed var(--border-default, #CCD1D5);
background-color: var(--interactive-weak);
&:hover{
border: 1px dashed var(--border-strong, #CCD1D5);
background-color: var(--interactive-default);
}
}
}

View file

@ -1,78 +1,94 @@
input.form-control,
textarea,
.input-control {
gap: 16px !important;
background: var(--base) !important;
border: 1px solid var(--slate7) !important;
border-radius: 6px;
margin-bottom: 4px !important;
color: var(--slate12) !important;
transition: none;
height: 35px;
padding-left: 0.4375rem;
padding-right: 0.4375rem;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
overflow-x: 'auto';
white-space: 'nowrap';
textarea,
.input-control {
gap: 16px !important;
background: var(--base) !important;
border: 1px solid var(--slate7) !important;
border-radius: 6px;
margin-bottom: 4px !important;
color: var(--slate12) !important;
transition: none;
height: 35px;
padding-left: 0.4375rem;
padding-right: 0.4375rem;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
overflow-x: 'auto';
white-space: 'nowrap';
&:hover {
background: var(--slate1) !important;
border: 1px solid var(--slate8) !important;
-webkit-box-shadow: none !important;
box-shadow: none !important;
outline: none;
}
&:focus-visible {
background: var(--indigo2) !important;
border: 1px solid var(--indigo9) !important;
box-shadow: none !important;
}
&.input-error-border {
border-color: #DB4324 !important;
}
&:-webkit-autofill {
box-shadow: 0 0 0 1000px var(--base) inset !important;
-webkit-text-fill-color: var(--slate12) !important;
&:hover {
background: var(--slate1) !important;
border: 1px solid var(--slate8) !important;
-webkit-box-shadow: none !important;
box-shadow: none !important;
outline: none;
box-shadow: 0 0 0 1000px var(--slate1) inset !important;
-webkit-text-fill-color: var(--slate12) !important;
}
&:focus-visible {
background: var(--indigo2) !important;
border: 1px solid var(--indigo9) !important;
box-shadow: none !important;
}
&.input-error-border {
border-color: #DB4324 !important;
}
&:-webkit-autofill {
box-shadow: 0 0 0 1000px var(--base) inset !important;
box-shadow: 0 0 0 1000px var(--indigo2) inset !important;
-webkit-text-fill-color: var(--slate12) !important;
&:hover {
box-shadow: 0 0 0 1000px var(--slate1) inset !important;
-webkit-text-fill-color: var(--slate12) !important;
}
&:focus-visible {
box-shadow: 0 0 0 1000px var(--indigo2) inset !important;
-webkit-text-fill-color: var(--slate12) !important;
}
}
}
.empty-key-value {
border-radius: 6px;
padding: 10px;
text-align: center;
width: 625px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
color: #687076;
font-size: 12px;
font-weight: 400;
line-height: 20px;
border: 1px dashed #E6E8EB;
}
}
.trash {
height: 32px;
display: flex;
justify-content: 'center';
align-items: 'center';
}
.empty-key-value {
border-radius: 6px;
padding: 10px;
text-align: center;
width: 625px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
color: #687076;
font-size: 12px;
font-weight: 400;
line-height: 20px;
border: 1px dashed #E6E8EB;
}
.trash {
height: 32px;
display: flex;
justify-content: 'center';
align-items: 'center';
}
.empty-version {
border-radius: 6px;
padding: 10px;
text-align: center;
width: auto;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
color: #687076;
font-size: 12px;
font-weight: 400;
line-height: 20px;
border: 1px dashed #E6E8EB;
}

View file

@ -1,5 +1,5 @@
export const APP_ERROR_TYPE = {
IMPORT_EXPORT_SERVICE: {
UNSUPPORTED_VERSION_ERROR: "Can't import higher version application to lower version",
UNSUPPORTED_VERSION_ERROR: 'Apps built on later versions of ToolJet cannot be imported',
},
};