Updated React to 18.2.0 (#5555)

* updated react to 18.2.0

* Updated Frontend Packages (#5569)

* Updated tabler icon and fixed react hot keys issue

* Fixed vulnerabilities

* Updated dom purify and yjs

* Reverted Eslint update

* React-big-calendar update

* Updated and fixed changes related to react-bootstrap

* Updated eslint package

* Fixes react-select-search ui

* Updated packages in root

* Updated & Fixed React-tooltip changes

* Updated and fixed changes related to react-router-dom

* Fixed copyToClipboard bug on usersTable

* Fixed folder popover issue and comment issue

* Fixed flickering issue on Editor

* Fixed routing and dark mode bugs

* Fixed app crash on page options click

* Fixed SVG issues in data sources

* Fixed calendar widget crash

* Fixed popover issue in table

* Fixed dark mode issue on react-select-search

* Fixed popover issue in tooljetdb table

* Fixed popover issue in pages

* Fixed search bar crash

* Fixes dark mode issue on react-select-search

* Resolved conflicts
This commit is contained in:
Kavin Venkatachalam 2023-03-20 17:04:24 +05:30 committed by GitHub
parent e6bae54130
commit 4c94de899d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
88 changed files with 45477 additions and 19257 deletions

View file

@ -55,7 +55,7 @@ module.exports = {
'jest/no-identical-title': 'error',
'jest/prefer-to-have-length': 'warn',
'jest/valid-expect': 'error',
'import/no-unresolved': ['error', { ignore: ['^@/', 'react-hot-toast'] }],
'import/no-unresolved': ['error', { ignore: ['^@/', 'react-hot-toast', 'react-i18next'] }],
'react/no-unknown-property': 'off',
},
settings: {

View file

@ -4,6 +4,7 @@ import Avatar from '../../../src/_ui/Avatar';
import Skeleton from 'react-loading-skeleton';
import cx from 'classnames';
import { Pagination } from '@/_components';
import { Tooltip } from 'react-tooltip';
const UsersTable = ({
isLoading,
@ -99,16 +100,20 @@ const UsersTable = ({
{user.status}
</small>
{user.status === 'invited' && 'invitation_token' in user ? (
<CopyToClipboard text={generateInvitationURL(user)} onCopy={invitationLinkCopyHandler}>
<img
data-tip="Copy invitation link"
className="svg-icon cursor-pointer"
src="assets/images/icons/copy.svg"
width="15"
height="15"
data-cy="copy-invitation-link"
></img>
</CopyToClipboard>
<>
<CopyToClipboard text={generateInvitationURL(user)} onCopy={invitationLinkCopyHandler}>
<img
data-tooltip-id="tooltip-for-copy-invitation-link"
data-tooltip-content="Copy invitation link"
className="svg-icon cursor-pointer"
src="assets/images/icons/copy.svg"
width="15"
height="15"
data-cy="copy-invitation-link"
></img>
</CopyToClipboard>
<Tooltip id="tooltip-for-copy-invitation-link" className="tooltip" />
</>
) : (
''
)}

60210
frontend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,133 +3,134 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@dnd-kit/core": "^6.0.5",
"@dnd-kit/sortable": "^7.0.1",
"@dnd-kit/utilities": "^3.2.0",
"@radix-ui/react-popover": "^1.0.2",
"@react-google-maps/api": "^2.1.1",
"@sentry/react": "^7.12.0",
"@sentry/tracing": "^7.12.0",
"@tabler/icons": "^1.91.1",
"@dnd-kit/core": "^6.0.7",
"@dnd-kit/sortable": "^7.0.2",
"@dnd-kit/utilities": "^3.2.1",
"@emoji-mart/data": "^1.1.2",
"@emoji-mart/react": "^1.1.1",
"@radix-ui/react-popover": "^1.0.3",
"@react-google-maps/api": "^2.18.1",
"@sentry/react": "^7.37.2",
"@sentry/tracing": "^7.37.2",
"@tabler/icons-react": "^2.4.0",
"@tooljet/plugins": "../plugins",
"@uiw/react-codemirror": "^3.0.6",
"@y-presence/react": "^2.0.0",
"array-move": "^3.0.1",
"axios": "^0.24.0",
"bootstrap": "^4.6.0",
"classnames": "^2.3.1",
"date-fns": "^2.28.0",
"deep-object-diff": "^1.1.7",
"dompurify": "^2.2.7",
"@y-presence/react": "^2.0.1",
"array-move": "^4.0.0",
"axios": "^1.3.3",
"bootstrap": "^5.2.3",
"classnames": "^2.3.2",
"date-fns": "^2.29.3",
"deep-object-diff": "^1.1.9",
"dompurify": "^3.0.0",
"draft-js": "^0.11.7",
"draft-js-export-html": "^1.4.1",
"driver.js": "^0.9.8",
"emoji-mart": "^3.0.1",
"focus-trap-react": "^10.0.0",
"fuse.js": "^6.4.6",
"history": "^4.9.0",
"html-loader": "^3.1.0",
"html-webpack-plugin": "^5.3.2",
"emoji-mart": "^5.5.2",
"focus-trap-react": "^10.0.2",
"fuse.js": "^6.6.2",
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.0",
"humps": "^2.0.1",
"i18next": "^21.8.14",
"i18next-browser-languagedetector": "^6.1.4",
"i18next-http-backend": "^1.4.1",
"immer": "^9.0.6",
"i18next": "^22.4.9",
"i18next-browser-languagedetector": "^7.0.1",
"i18next-http-backend": "^2.1.1",
"immer": "^9.0.19",
"immutability-helper": "^3.1.1",
"jspdf": "^2.5.1",
"jspdf-autotable": "^3.5.25",
"jspdf-autotable": "^3.5.28",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"moment-timezone": "^0.5.34",
"papaparse": "^5.3.0",
"plotly.js-basic-dist-min": "^1.58.4",
"psl": "^1.8.0",
"query-string": "^6.13.6",
"rc-slider": "^9.7.5",
"react": "^16.14.0",
"react-beautiful-dnd": "^13.1.0",
"react-big-calendar": "^0.38.0",
"react-bootstrap": "^1.5.2",
"react-burger-menu": "^3.0.8",
"react-checkbox-tree": "^1.7.3",
"react-circular-progressbar": "^2.0.4",
"moment": "^2.29.4",
"moment-timezone": "^0.5.40",
"papaparse": "^5.3.2",
"plotly.js-basic-dist-min": "^2.18.1",
"psl": "^1.9.0",
"query-string": "^8.1.0",
"rc-slider": "^10.1.1",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-big-calendar": "^1.6.5",
"react-bootstrap": "^2.7.2",
"react-burger-menu": "^3.0.9",
"react-checkbox-tree": "^1.8.0",
"react-circular-progressbar": "^2.1.0",
"react-color": "^2.19.3",
"react-copy-to-clipboard": "^5.0.3",
"react-datepicker": "^4.7.0",
"react-copy-to-clipboard": "^5.1.0",
"react-datepicker": "^4.10.0",
"react-dates": "^21.8.0",
"react-datetime": "^3.0.4",
"react-dnd": "^14.0.2",
"react-dnd-html5-backend": "^14.0.0",
"react-dom": "^16.14.0",
"react-dropzone": "^11.4.2",
"react-easy-sort": "^1.5.0",
"react-hot-toast": "^2.1.1",
"react-hotkeys-hook": "^3.4.4",
"react-i18next": "^11.18.3",
"react-datetime": "^3.2.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-easy-sort": "^1.5.1",
"react-google-login": "^5.2.2",
"react-hot-toast": "^2.4.0",
"react-hotkeys-hook": "^4.3.5",
"react-i18next": "^12.1.5",
"react-image-annotation": "^0.9.10",
"react-json-tree": "^0.16.1",
"react-json-tree": "^0.18.0",
"react-json-view": "^1.21.3",
"react-lazy-load-image-component": "^1.5.1",
"react-lazy-load-image-component": "^1.5.6",
"react-lazyload": "^3.2.0",
"react-loading-skeleton": "^2.2.0",
"react-mentions": "^4.3.0",
"react-multi-select-component": "^4.2.3",
"react-pdf": "^5.7.2",
"react-plotly.js": "^2.5.1",
"react-loading-skeleton": "^3.1.1",
"react-mentions": "^4.4.7",
"react-multi-select-component": "^4.3.4",
"react-pdf": "^6.2.2",
"react-plotly.js": "^2.6.0",
"react-qr-reader": "^2.2.1",
"react-rnd": "^10.3.0",
"react-router-breadcrumbs-hoc": "^4.1.0",
"react-router-dom": "^5.0.0",
"react-select": "^4.3.1",
"react-select-search": "^3.0.5",
"react-selecto": "^1.17.0",
"react-spring": "^9.2.4",
"react-table": "^7.6.3",
"react-table-plugins": "^1.3.1",
"react-tooltip": "^4.2.18",
"react-virtuoso": "^2.17.2",
"react-zoom-pan-pinch": "^2.1.3",
"rxjs": "^6.3.3",
"semver": "^5.7.1",
"superstruct": "^0.15.4",
"tinycolor2": "^1.4.2",
"react-rnd": "^10.4.1",
"react-router-dom": "^6.8.1",
"react-select": "^5.7.0",
"react-select-search": "^4.1.6",
"react-selecto": "^1.22.0",
"react-spring": "^9.6.1",
"react-table": "^7.8.0",
"react-table-plugins": "^1.3.2",
"react-tooltip": "^5.8.1",
"react-virtuoso": "^4.1.0",
"react-zoom-pan-pinch": "^2.6.1",
"rxjs": "^7.8.0",
"semver": "^7.3.8",
"superstruct": "^1.0.3",
"tinycolor2": "^1.6.0",
"url-join": "^5.0.0",
"uuid": "8.3.2",
"use-react-router-breadcrumbs": "^4.0.1",
"uuid": "9.0.0",
"xlsx": "^0.18.5",
"y-websocket": "^1.4.0",
"yjs": "^13.5.28",
"yup": "^0.27.0"
"y-websocket": "^1.4.5",
"yjs": "^13.5.46"
},
"devDependencies": {
"@babel/core": "^7.4.3",
"@babel/eslint-parser": "^7.18.9",
"@babel/plugin-transform-runtime": "^7.16.10",
"@babel/preset-env": "^7.18.10",
"@babel/core": "^7.20.12",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@svgr/webpack": "^5.5.0",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"babel-loader": "^8.0.5",
"@svgr/webpack": "^6.5.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"babel-loader": "^9.1.2",
"babel-plugin-console-source": "^2.0.5",
"babel-plugin-import": "^1.13.3",
"babel-plugin-import": "^1.13.6",
"compression-webpack-plugin": "^10.0.0",
"css-loader": "^6.5.1",
"esbuild": "^0.15.3",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-webpack": "^0.13.1",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-prettier": "^3.4.1",
"eslint-plugin-react": "^7.25.2",
"eslint-plugin-react-hooks": "^4.2.0",
"html-loader": "^3.1.0",
"html-webpack-plugin": "^5.3.2",
"jest": "^27.5.1",
"css-loader": "^6.7.3",
"esbuild": "^0.17.8",
"eslint": "^8.34.0",
"eslint-config-prettier": "^8.6.0",
"eslint-import-resolver-webpack": "^0.13.2",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.0",
"jest": "^29.4.2",
"node-sass": "^8.0.0",
"path": "^0.12.7",
"prettier": "^2.3.2",
"prettier": "^2.8.4",
"sass-loader": "^13.2.0",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.6",
@ -138,9 +139,33 @@
"webpack-dev-server": "^4.11.1"
},
"overrides": {
"@y-presence/react": {
"react": "16.14.0",
"react-dom": "16.14.0"
"react-dates": {
"react": "$react",
"react-dom": "$react-dom"
},
"react-google-login": {
"react": "$react",
"react-dom": "$react-dom"
},
"react-json-view": {
"react": "$react",
"react-dom": "$react-dom"
},
"react-lazyload": {
"react": "$react",
"react-dom": "$react-dom"
},
"react-qr-reader": {
"react": "$react",
"react-dom": "$react-dom"
},
"react-table-plugins": {
"react": "$react",
"react-dom": "$react-dom"
},
"react-image-annotation": {
"react": "$react",
"react-dom": "$react-dom"
}
},
"scripts": {

View file

@ -1,8 +1,8 @@
import React, { Suspense } from 'react';
// eslint-disable-next-line no-unused-vars
import config from 'config';
import { BrowserRouter, Route, Redirect } from 'react-router-dom';
import { history } from '@/_helpers';
import { Route, Routes, BrowserRouter } from 'react-router-dom';
import { withRouter } from '@/_hoc/withRouter';
import { authenticationService, tooljetService } from '@/_services';
import { PrivateRoute, AdminRoute } from '@/_components';
import { HomePage } from '@/HomePage';
@ -22,11 +22,21 @@ import { lt } from 'semver';
import Toast from '@/_ui/Toast';
import { VerificationSuccessInfoScreen } from '@/SuccessInfoScreen';
import '@/_styles/theme.scss';
import 'emoji-mart/css/emoji-mart.css';
import { AppLoader } from '@/AppLoader';
import SetupScreenSelfHost from '../SuccessInfoScreen/SetupScreenSelfHost';
import 'react-tooltip/dist/react-tooltip.css';
class App extends React.Component {
const AppWrapper = (props) => {
return (
<Suspense fallback={null}>
<BrowserRouter basename={window.public_config?.SUB_PATH || '/'}>
<AppWithRouter props={props} />
</BrowserRouter>
</Suspense>
);
};
class AppComponent extends React.Component {
constructor(props) {
super(props);
@ -55,11 +65,6 @@ class App extends React.Component {
});
}
logout = () => {
authenticationService.logout();
history.push('/login');
};
switchDarkMode = (newMode) => {
this.setState({ darkMode: newMode });
localStorage.setItem('darkMode', newMode);
@ -86,177 +91,149 @@ class App extends React.Component {
}
return (
<Suspense fallback={null}>
<BrowserRouter history={history} basename={window.public_config?.SUB_PATH || '/'}>
<div className={`main-wrapper ${darkMode ? 'theme-dark' : ''}`} data-cy="main-wrapper">
{updateAvailable && (
<div className="alert alert-info alert-dismissible" role="alert">
<h3 className="mb-1">Update available</h3>
<p>A new version of ToolJet has been released.</p>
<div className="btn-list">
<a
href="https://docs.tooljet.io/docs/setup/updating"
target="_blank"
className="btn btn-info"
rel="noreferrer"
>
Read release notes & update
</a>
<a
onClick={() => {
tooljetService.skipVersion();
this.setState({ updateAvailable: false });
}}
className="btn"
>
Skip this version
</a>
</div>
<>
<div className={`main-wrapper ${darkMode ? 'theme-dark' : ''}`} data-cy="main-wrapper">
{updateAvailable && (
<div className="alert alert-info alert-dismissible" role="alert">
<h3 className="mb-1">Update available</h3>
<p>A new version of ToolJet has been released.</p>
<div className="btn-list">
<a
href="https://docs.tooljet.io/docs/setup/updating"
target="_blank"
className="btn btn-info"
rel="noreferrer"
>
Read release notes & update
</a>
<a
onClick={() => {
tooljetService.skipVersion();
this.setState({ updateAvailable: false });
}}
className="btn"
>
Skip this version
</a>
</div>
)}
<PrivateRoute
</div>
)}
<Routes>
<Route
exact
path="/"
component={HomePage}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
/>
<Route path="/login/:organizationId" exact component={LoginPage} />
<Route path="/login" exact component={LoginPage} />
<Route path="/setup" exact component={(props) => <SetupScreenSelfHost {...props} darkMode={darkMode} />} />
<Route path="/sso/:origin/:configId" exact component={Oauth} />
<Route path="/sso/:origin" exact component={Oauth} />
<Route path="/signup" component={SignupPage} />
<Route path="/forgot-password" component={ForgotPassword} />
<Route
path="/reset-password/:token"
render={(props) => (
<Redirect
to={{
pathname: '/reset-password',
state: {
token: props.match.params.token,
},
}}
/>
)}
/>
<Route path="/reset-password" component={ResetPassword} />
<Route
path="/invitations/:token"
render={(props) => (
<Redirect
to={{
pathname: '/confirm',
state: {
token: props.match.params.token,
search: props.location.search,
},
}}
/>
)}
path="*"
element={
<PrivateRoute>
<HomePage switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
<Route path="/login/:organizationId" exact element={<LoginPage />} />
<Route path="/login" exact element={<LoginPage />} />
<Route path="/setup" exact element={<SetupScreenSelfHost {...this.props} darkMode={darkMode} />} />
<Route path="/sso/:origin/:configId" exact element={<Oauth />} />
<Route path="/sso/:origin" exact element={<Oauth />} />
<Route path="/signup" element={<SignupPage />} />
<Route path="/forgot-password" element={<ForgotPassword />} />
<Route path="/reset-password/:token" element={<ResetPassword />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route path="/invitations/:token" element={<VerificationSuccessInfoScreen />} />
<Route
path="/invitations/:token/workspaces/:organizationToken"
render={(props) => (
<Redirect
to={{
pathname: '/confirm',
state: {
token: props.match.params.token,
organizationToken: props.match.params.organizationToken,
search: props.location.search,
},
}}
/>
)}
element={<VerificationSuccessInfoScreen />}
/>
<Route path="/confirm" component={VerificationSuccessInfoScreen} />
<Route path="/confirm" element={<VerificationSuccessInfoScreen />} />
<Route
path="/organization-invitations/:token"
render={(props) => (
<Redirect
to={{
pathname: '/confirm-invite',
state: {
token: props.match.params.token,
search: props.location.search,
},
}}
/>
)}
element={<OrganizationInvitationPage {...this.props} darkMode={darkMode} />}
/>
<Route
path="/confirm-invite"
component={(props) => <OrganizationInvitationPage {...props} darkMode={darkMode} />}
element={<OrganizationInvitationPage {...this.props} darkMode={darkMode} />}
/>
<PrivateRoute
<Route
exact
path="/apps/:id/:pageHandle?"
component={AppLoader}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
path="/apps/:id/:pageHandle?/*"
element={
<PrivateRoute>
<AppLoader switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
<PrivateRoute
<Route
exact
path="/applications/:id/versions/:versionId/:pageHandle?"
component={Viewer}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
element={
<PrivateRoute>
<Viewer switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
<PrivateRoute
<Route
exact
path="/applications/:slug/:pageHandle?"
component={Viewer}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
element={
<PrivateRoute>
<Viewer switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
<PrivateRoute
<Route
exact
path="/oauth2/authorize"
component={Authorize}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
element={
<PrivateRoute>
<Authorize switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
<PrivateRoute
<Route
exact
path="/workspace-settings"
component={OrganizationSettings}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
element={
<PrivateRoute>
<OrganizationSettings switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
<PrivateRoute
<Route
exact
path="/settings"
component={SettingsPage}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
element={
<PrivateRoute>
<SettingsPage switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
{window.public_config?.ENABLE_TOOLJET_DB == 'true' && (
<PrivateRoute
<Route
exact
path="/database"
component={TooljetDatabase}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
element={
<PrivateRoute>
<TooljetDatabase switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
)}
{window.public_config?.ENABLE_MARKETPLACE_FEATURE === 'true' && (
<AdminRoute
<Route
exact
path="/integrations"
component={MarketplacePage}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
element={
<AdminRoute>
<MarketplacePage switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</AdminRoute>
}
/>
)}
</div>
</BrowserRouter>
</Routes>
</div>
<Toast toastOptions={toastOptions} />
</Suspense>
</>
);
}
}
export { App };
export const App = AppWrapper;
const AppWithRouter = withRouter(AppComponent);

View file

@ -7,10 +7,12 @@ import config from 'config';
import { safelyParseJSON, stripTrailingSlash } from '@/_helpers/utils';
import { toast } from 'react-hot-toast';
import useRouter from '@/_hooks/use-router';
import { useParams } from 'react-router-dom';
const AppLoaderComponent = (props) => {
const router = useRouter();
const appId = props.match.params.id;
const params = useParams();
const appId = params.id;
const currentUser = authenticationService.currentUserValue;
// eslint-disable-next-line react-hooks/exhaustive-deps

View file

@ -12,6 +12,7 @@ import EyeHide from '../../assets/images/onboardingassets/Icons/EyeHide';
import EyeShow from '../../assets/images/onboardingassets/Icons/EyeShow';
import Spinner from '@/_ui/Spinner';
import { LinkExpiredInfoScreen } from '../SuccessInfoScreen/LinkExpiredInfoScreen';
import { withRouter } from '@/_hoc/withRouter';
class OrganizationInvitationPageComponent extends React.Component {
constructor(props) {
super(props);
@ -27,8 +28,8 @@ class OrganizationInvitationPageComponent extends React.Component {
};
this.formRef = React.createRef(null);
this.single_organization = window.public_config?.DISABLE_MULTI_WORKSPACE === 'true';
this.organizationId = new URLSearchParams(props?.location?.state?.search).get('oid');
this.source = new URLSearchParams(props?.location?.state?.search).get('source');
this.organizationId = new URLSearchParams(props?.location?.search).get('oid');
this.source = new URLSearchParams(props?.location?.search).get('source');
}
componentDidMount() {
@ -61,7 +62,7 @@ class OrganizationInvitationPageComponent extends React.Component {
}
authenticationService
.verifyOrganizationToken(this.props?.location?.state?.token)
.verifyOrganizationToken(this.props?.params?.token)
.then((data) => {
this.setState({ userDetails: data });
if (data?.email !== '') {
@ -85,7 +86,7 @@ class OrganizationInvitationPageComponent extends React.Component {
e.preventDefault();
const isSetPassword = !!this.state?.userDetails?.onboarding_details?.password;
const token = this.props?.location?.state?.token;
const token = this.props?.params?.token;
const { password } = this.state;
this.setState({ isLoading: true });
@ -116,9 +117,9 @@ class OrganizationInvitationPageComponent extends React.Component {
json.then((res) => {
authenticationService.updateUser(res?.user);
authenticationService.deleteLoginOrganizationId();
this.props.history.push('/login');
this.props.navigate('/login');
});
} else this.props.history.push('/login');
} else this.props.navigate('/login');
}
})
.catch(() => {
@ -402,4 +403,4 @@ class OrganizationInvitationPageComponent extends React.Component {
}
}
export const OrganizationInvitationPage = withTranslation()(OrganizationInvitationPageComponent);
export const OrganizationInvitationPage = withTranslation()(withRouter(OrganizationInvitationPageComponent));

View file

@ -99,7 +99,7 @@ export const BoxShadow = ({ value, onChange, forceCodeBox, cyLabel }) => {
style={{ width: '350px', maxWidth: '350px' }}
className={`${darkMode && 'popover-dark-themed theme-dark'} shadow`}
>
<Popover.Content>
<Popover.Body>
<>
{input.map((item) => (
<div className="row" key={item}>
@ -142,7 +142,7 @@ export const BoxShadow = ({ value, onChange, forceCodeBox, cyLabel }) => {
Clear
</button>
</>
</Popover.Content>
</Popover.Body>
</Popover>
);
};

View file

@ -1,11 +1,13 @@
import React from 'react';
import cx from 'classnames';
import { Picker } from 'emoji-mart';
import data from '@emoji-mart/data/sets/14/apple.json';
import Picker from '@emoji-mart/react';
import TextareaMentions from '@/_ui/Mentions';
import Button from '@/_ui/Button';
import usePopover from '@/_hooks/use-popover';
import { useHotkeys } from 'react-hotkeys-hook';
// eslint-disable-next-line import/no-unresolved
import { useTranslation } from 'react-i18next';
function CommentFooter({
@ -38,12 +40,18 @@ function CommentFooter({
setOpen(false);
};
useHotkeys('+enter, control+enter', () => handleClick());
useHotkeys('meta+enter, control+enter', () => handleClick());
const darkMode = localStorage.getItem('darkMode') === 'true';
return (
<>
<div {...content} className={open ? 'show' : 'hide'}>
<Picker theme={darkMode ? 'dark' : 'light'} style={{ width: 320 }} set="apple" onSelect={addEmoji} />
<Picker
data={data}
theme={darkMode ? 'dark' : 'light'}
style={{ width: 320 }}
set="apple"
onEmojiSelect={addEmoji}
/>
</div>
<div className="card-footer">
<div className="row align-items-center">

View file

@ -36,6 +36,7 @@ const Comment = ({ socket, x, y, threadId, user = {}, isResolved, fetchThreads,
}, []);
React.useLayoutEffect(() => {
// eslint-disable-next-line no-unsafe-optional-chaining
const { left } = trigger?.ref?.current?.getBoundingClientRect();
if (left < 460) setPlacement('right');
@ -54,7 +55,7 @@ const Comment = ({ socket, x, y, threadId, user = {}, isResolved, fetchThreads,
} else {
// resetting the query param
// react router updates the url with the set basename resulting invalid url unless replaced
router.push(window.location.pathname.replace(window.public_config?.SUB_PATH, '/'));
router.history(window.location.pathname.replace(window.public_config?.SUB_PATH, '/'));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);

View file

@ -29,6 +29,7 @@ export const ButtonGroup = function Button({
const [defaultActive, setDefaultActive] = useState(defaultSelected);
const [data, setData] = useState(
// eslint-disable-next-line no-unsafe-optional-chaining
values?.length <= labels?.length ? [...labels, ...values?.slice(labels?.length)] : labels
);
// data is used as state to show what to display , club of label+values / values
@ -39,6 +40,7 @@ export const ButtonGroup = function Button({
useEffect(() => {
if (labels?.length < values?.length) {
// eslint-disable-next-line no-unsafe-optional-chaining
setData([...labels, ...values?.slice(labels?.length)]);
} else {
setData(labels);

View file

@ -119,11 +119,6 @@ export const Calendar = function ({
},
};
//! hack
if (exposedVariables.currentDate === undefined) {
setExposedVariable('currentDate', moment(defaultDate).format(properties.dateFormat));
}
return (
<div id={id} style={{ display: styles.visibility ? 'block' : 'none' }} data-cy={dataCy}>
<ReactCalendar

View file

@ -1,11 +1,12 @@
import React, { useState, useEffect } from 'react';
// eslint-disable-next-line import/no-unresolved
import * as Icons from '@tabler/icons';
import * as Icons from '@tabler/icons-react';
import cx from 'classnames';
export const Icon = ({ properties, styles, fireEvent, width, height, registerAction, darkMode, dataCy }) => {
const { icon } = properties;
const { iconColor, visibility } = styles;
// eslint-disable-next-line import/namespace
const IconElement = Icons[icon];
const color = iconColor === '#000' ? (darkMode ? '#fff' : '#000') : iconColor;
@ -42,6 +43,7 @@ export const Icon = ({ properties, styles, fireEvent, width, height, registerAct
event.stopPropagation();
fireEvent('onHover');
}}
stroke={1.5}
/>
</div>
);

View file

@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import Slider, { Range } from 'rc-slider';
import Slider from 'rc-slider';
import 'rc-slider/assets/index.css';
export const RangeSlider = function RangeSlider({ height, properties, styles, setExposedVariable, fireEvent, dataCy }) {
@ -64,7 +64,8 @@ export const RangeSlider = function RangeSlider({ height, properties, styles, se
return (
<div style={computedStyles} className="range-slider" data-cy={dataCy}>
{enableTwoHandle ? (
<Range
<Slider
range
min={min}
max={max}
defaultValue={toArray(rangeValue)}

View file

@ -37,7 +37,7 @@ export const CustomSelect = ({ options, value, multiple, onChange, darkMode, isE
onChange={onChange}
multiple={multiple}
placeholder={t('globals.select', 'Select') + '...'}
className={`${darkMode ? 'select-search-dark' : 'select-search'}`}
className={'select-search'}
/>
</div>
);

View file

@ -28,6 +28,7 @@ import generateColumnsData from './columns';
import generateActionsData from './columns/actions';
import autogenerateColumns from './columns/autogenerateColumns';
import IndeterminateCheckbox from './IndeterminateCheckbox';
// eslint-disable-next-line import/no-unresolved
import { useTranslation } from 'react-i18next';
// eslint-disable-next-line import/no-unresolved
import JsPDF from 'jspdf';
@ -35,7 +36,7 @@ import JsPDF from 'jspdf';
import 'jspdf-autotable';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
// eslint-disable-next-line import/no-unresolved
import { IconEyeOff } from '@tabler/icons';
import { IconEyeOff } from '@tabler/icons-react';
import * as XLSX from 'xlsx/xlsx.mjs';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Popover from 'react-bootstrap/Popover';
@ -43,6 +44,7 @@ import { useMounted } from '@/_hooks/use-mount';
import GenerateEachCellValue from './GenerateEachCellValue';
// eslint-disable-next-line import/no-unresolved
import { toast } from 'react-hot-toast';
import { Tooltip } from 'react-tooltip';
export function Table({
id,
@ -348,6 +350,9 @@ export function Table({
darkMode,
] // Hack: need to fix
);
console.log('columns--- ', columns);
const data = useMemo(
() => tableData,
[
@ -598,7 +603,7 @@ export function Table({
className={`${darkMode && 'popover-dark-themed theme-dark'} shadow table-widget-download-popup`}
placement="bottom"
>
<Popover.Content>
<Popover.Body>
<div className="d-flex flex-column">
<span data-cy={`option-download-CSV`} className="cursor-pointer" onClick={() => exportData('csv', true)}>
Download as CSV
@ -618,7 +623,7 @@ export function Table({
Download as PDF
</span>
</div>
</Popover.Content>
</Popover.Body>
</Popover>
);
}
@ -635,7 +640,6 @@ export function Table({
borderRadius: Number.parseFloat(borderRadius),
}}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component, event);
}}
ref={tableRef}
@ -661,19 +665,34 @@ export function Table({
)}
<div>
{showFilterButton && (
<span data-tip="Filter data" className="btn btn-light btn-sm p-1 mx-1" onClick={() => showFilters()}>
<img src="assets/images/icons/filter.svg" width="15" height="15" />
{tableDetails.filterDetails.filters.length > 0 && (
<a className="badge bg-azure" style={{ width: '4px', height: '4px', marginTop: '5px' }}></a>
)}
</span>
<>
<span
className="btn btn-light btn-sm p-1 mx-1"
onClick={() => showFilters()}
data-tooltip-id="tooltip-for-filter-data"
data-tooltip-content="Filter data"
>
<img src="assets/images/icons/filter.svg" width="15" height="15" />
{tableDetails.filterDetails.filters.length > 0 && (
<a className="badge bg-azure" style={{ width: '4px', height: '4px', marginTop: '5px' }}></a>
)}
</span>
<Tooltip id="tooltip-for-filter-data" className="tooltip" />
</>
)}
{showDownloadButton && (
<OverlayTrigger trigger="click" overlay={downlaodPopover()} rootClose={true} placement={'bottom-end'}>
<span data-tip="Download" className="btn btn-light btn-sm p-1">
<img src="assets/images/icons/download.svg" width="15" height="15" />
</span>
</OverlayTrigger>
<>
<OverlayTrigger trigger="click" overlay={downlaodPopover()} rootClose={true} placement={'bottom-end'}>
<span
className="btn btn-light btn-sm p-1"
data-tooltip-id="tooltip-for-download"
data-tooltip-content="Download"
>
<img src="assets/images/icons/download.svg" width="15" height="15" />
</span>
</OverlayTrigger>
<Tooltip id="tooltip-for-download" className="tooltip" />
</>
)}
{!hideColumnSelectorButton && (
<OverlayTrigger

View file

@ -1,6 +1,6 @@
import React from 'react';
import _ from 'lodash';
import SelectSearch, { fuzzySearch } from 'react-select-search';
import SelectSearch from 'react-select-search';
import { resolveReferences, validateWidget } from '@/_helpers/utils';
import { CustomSelect } from '../CustomSelect';
import { Tags } from '../Tags';
@ -280,10 +280,10 @@ export default function generateColumnsData({
onChange={(value) => {
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
}}
filterOptions={fuzzySearch}
fuzzySearch
placeholder={t('globals.select', 'Select') + '...'}
disabled={!column.isEditable}
className={`${darkMode ? 'select-search-dark' : 'select-search'}`}
className={'select-search'}
/>
<div className={`invalid-feedback ${isValid ? '' : 'd-flex'}`}>{validationError}</div>
</div>

View file

@ -68,11 +68,11 @@ export const Container = ({
const canvasRef = useRef(null);
const focusedParentIdRef = useRef(undefined);
useHotkeys('+z, control+z', () => handleUndo());
useHotkeys('+shift+z, control+shift+z', () => handleRedo());
useHotkeys('meta+z, control+z', () => handleUndo());
useHotkeys('meta+shift+z, control+shift+z', () => handleRedo());
useHotkeys(
'+v, control+v',
'meta+v, control+v',
() => {
if (isContainerFocused) {
navigator.clipboard.readText().then((cliptext) => {

View file

@ -176,15 +176,9 @@ export const DraggableBox = function DraggableBox({
};
const layoutData = inCanvas ? layouts[currentLayout] || defaultData : defaultData;
const [currentLayoutOptions, setCurrentLayoutOptions] = useState(layoutData);
useEffect(() => {
console.log(layoutData);
setCurrentLayoutOptions(layoutData);
}, [layoutData.height, layoutData.width, layoutData.left, layoutData.top, currentLayout]);
const gridWidth = canvasWidth / 43;
const width = (canvasWidth * currentLayoutOptions.width) / 43;
const width = (canvasWidth * layoutData.width) / 43;
const configWidgetHandlerForModalComponent =
!isSelectedComponent &&
@ -217,11 +211,11 @@ export const DraggableBox = function DraggableBox({
dragGrid={[gridWidth, 10]}
size={{
width: width,
height: currentLayoutOptions.height,
height: layoutData.height,
}}
position={{
x: currentLayoutOptions ? (currentLayoutOptions.left * canvasWidth) / 100 : 0,
y: currentLayoutOptions ? currentLayoutOptions.top : 0,
x: layoutData ? (layoutData.left * canvasWidth) / 100 : 0,
y: layoutData ? layoutData.top : 0,
}}
defaultSize={{}}
className={`resizer ${
@ -241,7 +235,7 @@ export const DraggableBox = function DraggableBox({
disableDragging={mode !== 'edit' || readOnly}
onDragStop={(e, direction) => {
setDragging(false);
onDragStop(e, id, direction, currentLayout, currentLayoutOptions);
onDragStop(e, id, direction, currentLayout, layoutData);
}}
cancel={`div.table-responsive.jet-data-table, div.calendar-widget, div.text-input, .textarea, .map-widget, .range-slider, .kanban-container`}
onDragStart={(e) => e.stopPropagation()}
@ -261,9 +255,9 @@ export const DraggableBox = function DraggableBox({
id={id}
removeComponent={removeComponent}
component={component}
position={currentLayoutOptions.top < 15 ? 'bottom' : 'top'}
widgetTop={currentLayoutOptions.top}
widgetHeight={currentLayoutOptions.height}
position={layoutData.top < 15 ? 'bottom' : 'top'}
widgetTop={layoutData.top}
widgetHeight={layoutData.height}
isMultipleComponentsSelected={isMultipleComponentsSelected}
configWidgetHandlerForModalComponent={configWidgetHandlerForModalComponent}
/>
@ -273,7 +267,7 @@ export const DraggableBox = function DraggableBox({
component={component}
id={id}
width={width}
height={currentLayoutOptions.height - 4}
height={layoutData.height - 4}
mode={mode}
changeCanDrag={changeCanDrag}
inCanvas={inCanvas}

View file

@ -32,7 +32,7 @@ import {
removeSelectedComponent,
} from '@/_helpers/appUtils';
import { Confirm } from './Viewer/Confirm';
import ReactTooltip from 'react-tooltip';
import { Tooltip as ReactTooltip } from 'react-tooltip';
import CommentNotifications from './CommentNotifications';
import { WidgetManager } from './WidgetManager';
import Fuse from 'fuse.js';
@ -53,6 +53,8 @@ import { v4 as uuid } from 'uuid';
import Skeleton from 'react-loading-skeleton';
import EmptyQueriesIllustration from '@assets/images/icons/no-queries-added.svg';
import EditorHeader from './Header';
import '@/_styles/editor/react-select-search.scss';
import { withRouter } from '@/_hoc/withRouter';
setAutoFreeze(false);
enablePatches();
@ -61,9 +63,9 @@ class EditorComponent extends React.Component {
constructor(props) {
super(props);
const appId = this.props.match.params.id;
const appId = this.props.params.id;
const pageHandle = this.props.match.params.pageHandle;
const pageHandle = this.props.params.pageHandle;
const currentUser = authenticationService.currentUserValue;
@ -174,7 +176,7 @@ class EditorComponent extends React.Component {
componentDidMount() {
this.autoSave();
this.fetchApps(0);
this.fetchApp(this.props.match.params.pageHandle);
this.fetchApp(this.props.params.pageHandle);
this.fetchOrgEnvironmentVariables();
this.initComponentVersioning();
this.initRealtimeSave();
@ -402,7 +404,7 @@ class EditorComponent extends React.Component {
};
fetchApp = (startingPageHandle) => {
const appId = this.props.match.params.id;
const appId = this.props.params.id;
const callBack = async (data) => {
let dataDefinition = defaults(data.definition, this.defaultDefinition);
@ -1627,7 +1629,7 @@ class EditorComponent extends React.Component {
const queryParamsString = queryParams.map(([key, value]) => `${key}=${value}`).join('&');
this.props.history.push(`/apps/${this.state.appId}/${handle}?${queryParamsString}`);
this.props.navigate(`/apps/${this.state.appId}/${handle}?${queryParamsString}`);
const { globals: existingGlobals } = this.state.currentState;
@ -1765,7 +1767,6 @@ class EditorComponent extends React.Component {
return (
<div className="editor wrapper">
<ReactTooltip type="dark" effect="solid" eventOff="click" delayShow={250} />
<Confirm
show={queryConfirmationList.length > 0}
message={`Do you want to run this query - ${queryConfirmationList[0]?.queryName}?`}
@ -2037,11 +2038,11 @@ class EditorComponent extends React.Component {
onClick={() => {
this.handleAddNewQuery(setSaveConfirmation, setCancelData);
}}
data-tooltip-id="tooltip-for-add-query"
data-tooltip-content="Add new query"
>
<span
className={` d-flex query-manager-btn-svg-wrapper align-items-center query-icon-wrapper`}
data-tip="Add new query"
data-class=""
>
<svg
width="auto"
@ -2133,6 +2134,7 @@ class EditorComponent extends React.Component {
</>
)}
</QueryPanel>
<ReactTooltip id="tooltip-for-add-query" className="tooltip" />
</div>
<div className="editor-sidebar">
<EditorKeyHooks
@ -2197,4 +2199,4 @@ class EditorComponent extends React.Component {
}
}
export const Editor = withTranslation()(EditorComponent);
export const Editor = withTranslation()(withRouter(EditorComponent));

View file

@ -21,6 +21,7 @@ export const EditorKeyHooks = ({
cloneComponents();
break;
case 'KeyC':
console.log('copyComponent');
copyComponents();
break;
case 'KeyX':
@ -32,7 +33,7 @@ export const EditorKeyHooks = ({
};
useKeyHooks(
['up, down, left, right', 'esc', 'backspace', 'cmd+d, ctrl+d, cmd+c, ctrl+c, cmd+x, ctrl+x'],
['up, down, left, right', 'esc', 'backspace', 'meta+d, ctrl+d, meta+c, ctrl+c, meta+x, ctrl+x'],
handleHotKeysCallback
);

View file

@ -1,7 +1,5 @@
import React from 'react';
import cx from 'classnames';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Popover from 'react-bootstrap/Popover';
import { SketchPicker } from 'react-color';
import { Confirm } from '../Viewer/Confirm';
import { HeaderSection } from '@/_ui/LeftSidebar';
@ -11,6 +9,7 @@ import { CodeHinter } from '../CodeBuilder/CodeHinter';
import { resolveReferences } from '@/_helpers/utils';
import { useTranslation } from 'react-i18next';
import _ from 'lodash';
import Popover from '@/_ui/Popover';
export const GlobalSettings = ({
globalSettings,
@ -49,8 +48,8 @@ export const GlobalSettings = ({
}, [JSON.stringify(resolveReferences(backgroundFxQuery, realState))]);
const popoverContent = (
<Popover id="global-settings-popover" className={cx({ 'theme-dark': darkMode })}>
<Popover.Content bsPrefix="global-settings-popover">
<div id="global-settings-popover" className={cx({ 'theme-dark': darkMode })}>
<div bsPrefix="global-settings-popover">
<HeaderSection darkMode={darkMode}>
<HeaderSection.PanelHeader title="Global settings" />
</HeaderSection>
@ -207,8 +206,8 @@ export const GlobalSettings = ({
</div>
</div>
</div>
</Popover.Content>
</Popover>
</div>
</div>
);
return (
@ -224,16 +223,15 @@ export const GlobalSettings = ({
onCancel={() => setConfirmationShow(false)}
darkMode={darkMode}
/>
<OverlayTrigger
onToggle={(show) => {
<Popover
handleToggle={(show) => {
if (show) setShow('settings');
else setShow('');
}}
rootClose
trigger="click"
placement="bottom"
overlay={popoverContent}
containerPadding={50}
popoverContentClassName="p-0 sidebar-h-100-popover"
side="bottom"
popoverContent={popoverContent}
popoverContentHeight="auto"
>
<LeftSidebarItem
selectedSidebarItem={show}
@ -241,7 +239,7 @@ export const GlobalSettings = ({
className={cx(`cursor-pointer sidebar-global-settings`)}
tip="Settings"
/>
</OverlayTrigger>
</Popover>
</>
);
};

View file

@ -1,5 +1,6 @@
import React from 'react';
import cx from 'classnames';
import { Tooltip } from 'react-tooltip';
function HeaderActions({ handleUndo, canUndo, handleRedo, canRedo, currentLayout, toggleCurrentLayout }) {
const darkMode = localStorage.getItem('darkMode') === 'true';
@ -68,7 +69,6 @@ function HeaderActions({ handleUndo, canUndo, handleRedo, canRedo, currentLayout
disabled: !canUndo,
})}
width="44"
data-tip="undo"
height="44"
viewBox="0 0 24 24"
strokeWidth="1.5"
@ -76,6 +76,8 @@ function HeaderActions({ handleUndo, canUndo, handleRedo, canRedo, currentLayout
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
data-tooltip-id="tooltip-for-undo"
data-tooltip-content="Undo"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none">
<title>undo</title>
@ -86,7 +88,6 @@ function HeaderActions({ handleUndo, canUndo, handleRedo, canRedo, currentLayout
</svg>
<svg
title="redo"
data-tip="redo"
onClick={handleRedo}
xmlns="http://www.w3.org/2000/svg"
className={cx('redo-button cursor-pointer icon icon-tabler icon-tabler-arrow-forward-up', {
@ -100,12 +101,16 @@ function HeaderActions({ handleUndo, canUndo, handleRedo, canRedo, currentLayout
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
data-tooltip-id="tooltip-for-redo"
data-tooltip-content="Redo"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none">
<title>redo</title>
</path>
<path d="M15 13l4 -4l-4 -4m4 4h-11a4 4 0 0 0 0 8h1" />
</svg>
<Tooltip id="tooltip-for-undo" className="tooltip" />
<Tooltip id="tooltip-for-redo" className="tooltip" />
</div>
);
}

View file

@ -6,7 +6,7 @@ import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Popover from 'react-bootstrap/Popover';
import { SearchBox } from '@/_components/SearchBox';
// eslint-disable-next-line import/no-unresolved
import * as Icons from '@tabler/icons';
import * as Icons from '@tabler/icons-react';
import { VirtuosoGrid } from 'react-virtuoso';
export function Icon({ componentMeta, darkMode, ...restProps }) {
@ -47,10 +47,10 @@ export function Icon({ componentMeta, darkMode, ...restProps }) {
style={{ width: '460px', maxWidth: '460px' }}
className={`${darkMode && 'popover-dark-themed theme-dark'} shadow icon-widget-popover`}
>
<Popover.Title>
<Popover.Header>
<SearchBox onSubmit={searchIcon} width="100%" />
</Popover.Title>
<Popover.Content>
</Popover.Header>
<Popover.Body>
<div className="row">
{
<VirtuosoGrid
@ -59,7 +59,9 @@ export function Icon({ componentMeta, darkMode, ...restProps }) {
listClassName="icon-list-wrapper"
itemClassName="icon-list"
itemContent={(index) => {
if (filteredIcons[index] === undefined) return null;
if (filteredIcons[index] === undefined || filteredIcons[index] === 'createReactComponent')
return null;
// eslint-disable-next-line import/namespace
const IconElement = Icons[filteredIcons[index]];
return (
<div
@ -71,7 +73,7 @@ export function Icon({ componentMeta, darkMode, ...restProps }) {
>
<IconElement
color={`${darkMode ? '#fff' : '#000'}`}
stroke={3}
stroke={1.5}
strokeLinejoin="miter"
style={{ width: '24px', height: '24px' }}
/>
@ -81,13 +83,14 @@ export function Icon({ componentMeta, darkMode, ...restProps }) {
/>
}
</div>
</Popover.Content>
</Popover.Body>
</Popover>
);
};
function renderIconPicker() {
const icon = component.component.definition.properties.icon;
// eslint-disable-next-line import/namespace
const IconElement = Icons[icon.value];
return (
<>
@ -109,7 +112,7 @@ export function Icon({ componentMeta, darkMode, ...restProps }) {
<div className="col-auto">
<IconElement
color={`${darkMode ? '#fff' : '#000'}`}
stroke={2}
stroke={1.5}
strokeLinejoin="miter"
style={{ width: '20px', height: '20px' }}
/>

View file

@ -8,7 +8,7 @@ import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Popover from 'react-bootstrap/Popover';
import { Color } from '../Elements/Color';
import SelectSearch, { fuzzySearch } from 'react-select-search';
import SelectSearch from 'react-select-search';
import { v4 as uuidv4 } from 'uuid';
import { EventManager } from '../EventManager';
import { CodeHinter } from '../../CodeBuilder/CodeHinter';
@ -167,13 +167,13 @@ class TableComponent extends React.Component {
];
return (
<Popover id="popover-basic-2" className={`${this.props.darkMode && 'popover-dark-themed theme-dark'} shadow`}>
<Popover.Content>
<Popover.Body>
<div className="field mb-2" data-cy={`dropdown-column-type`}>
<label data-cy={`label-column-type`} className="form-label">
{this.props.t('widget.Table.columnType', 'Column type')}
</label>
<SelectSearch
className={`${this.props.darkMode ? 'select-search-dark' : 'select-search'}`}
className={`${this.props.darkMode ? 'select-search' : 'select-search'}`}
options={[
{ name: 'Default', value: 'default' },
{ name: 'String', value: 'string' },
@ -195,7 +195,7 @@ class TableComponent extends React.Component {
onChange={(value) => {
this.onColumnItemChange(index, 'columnType', value);
}}
filterOptions={fuzzySearch}
fuzzySearch
placeholder={this.props.t('globals.select', 'Select') + '...'}
/>
</div>
@ -220,7 +220,7 @@ class TableComponent extends React.Component {
{this.props.t('widget.Table.overflow', 'Overflow')}
</label>
<SelectSearch
className={`${this.props.darkMode ? 'select-search-dark' : 'select-search'}`}
className={'select-search'}
options={[
{ name: 'Wrap', value: 'wrap' },
{ name: 'Scroll', value: 'scroll' },
@ -232,7 +232,7 @@ class TableComponent extends React.Component {
onChange={(value) => {
this.onColumnItemChange(index, 'textWrap', value);
}}
filterOptions={fuzzySearch}
fuzzySearch
placeholder={this.props.t('globals.select', 'Select') + '...'}
/>
</div>
@ -550,7 +550,7 @@ class TableComponent extends React.Component {
</label>
<div data-cy={`input-parse-timezone`} className="field mb-2">
<SelectSearch
className={`${this.props.darkMode ? 'select-search-dark' : 'select-search'}`}
className={'select-search'}
options={timeZoneOptions}
value={column.timeZoneValue}
search={true}
@ -558,7 +558,7 @@ class TableComponent extends React.Component {
onChange={(value) => {
this.onColumnItemChange(index, 'timeZoneValue', value);
}}
filterOptions={fuzzySearch}
fuzzySearch
placeholder="Select.."
/>
</div>
@ -567,7 +567,7 @@ class TableComponent extends React.Component {
</label>
<div ata-cy={`input-display-time-zone`} className="field mb-2">
<SelectSearch
className={`${this.props.darkMode ? 'select-search-dark' : 'select-search'}`}
className={'select-search'}
options={timeZoneOptions}
value={column.timeZoneDisplay}
search={true}
@ -575,7 +575,7 @@ class TableComponent extends React.Component {
onChange={(value) => {
this.onColumnItemChange(index, 'timeZoneDisplay', value);
}}
filterOptions={fuzzySearch}
fuzzySearch
placeholder="Select.."
/>
</div>
@ -641,7 +641,7 @@ class TableComponent extends React.Component {
<div data-cy={`input-and-label-object-fit`} className="field mb-2">
<label className="form-label">{this.props.t('widget.Table.objectFit', 'Object fit')}</label>
<SelectSearch
className={`${this.props.darkMode ? 'select-search-dark' : 'select-search'}`}
className={'select-search'}
options={[
{ name: 'Cover', value: 'cover' },
{ name: 'Contain', value: 'contain' },
@ -653,7 +653,7 @@ class TableComponent extends React.Component {
onChange={(value) => {
this.onColumnItemChange(index, 'objectFit', value);
}}
filterOptions={fuzzySearch}
fuzzySearch
placeholder={this.props.t('Select') + '...'}
/>
</div>
@ -674,7 +674,7 @@ class TableComponent extends React.Component {
</span>
</div>
)}
</Popover.Content>
</Popover.Body>
</Popover>
);
};
@ -690,7 +690,7 @@ class TableComponent extends React.Component {
return (
<Popover id="popover-basic" className={`${this.props.darkMode && 'popover-dark-themed theme-dark'} shadow`}>
<Popover.Content>
<Popover.Body>
<div className="field mb-2">
<label data-cy={`label-action-button-text`} className="form-label">
{this.props.t('widget.Table.buttonText', 'Button Text')}
@ -711,7 +711,7 @@ class TableComponent extends React.Component {
{this.props.t('widget.Table.buttonPosition', 'Button Position')}
</label>
<SelectSearch
className={`${this.props.darkMode ? 'select-search-dark' : 'select-search'}`}
className={'select-search'}
options={[
{ name: 'Left', value: 'left' },
{ name: 'Right', value: 'right' },
@ -722,7 +722,7 @@ class TableComponent extends React.Component {
onChange={(value) => {
this.onActionButtonPropertyChanged(index, 'position', value);
}}
filterOptions={fuzzySearch}
fuzzySearch
placeholder="Select position"
/>
</div>
@ -760,7 +760,7 @@ class TableComponent extends React.Component {
<button className="btn btn-sm btn-outline-danger mt-2 col" onClick={() => this.removeAction(index)}>
{this.props.t('widget.Table.remove', 'Remove')}
</button>
</Popover.Content>
</Popover.Body>
</Popover>
);
};

View file

@ -1,6 +1,7 @@
import React from 'react';
import { ToolTip } from './Components/ToolTip';
import SelectSearch, { fuzzySearch } from 'react-select-search';
import SelectSearch from 'react-select-search';
// eslint-disable-next-line import/no-unresolved
import { useTranslation } from 'react-i18next';
export const Select = ({ param, definition, onChange, paramType, componentMeta }) => {
@ -18,7 +19,7 @@ export const Select = ({ param, definition, onChange, paramType, componentMeta }
value={value}
search={true}
onChange={(newVal) => onChange(param, 'value', newVal, paramType)}
filterOptions={fuzzySearch}
fuzzySearch
placeholder={t('globals.select', 'Select') + '...'}
/>
</div>

View file

@ -208,7 +208,7 @@ export const EventManager = ({
className={`${darkMode && 'popover-dark-themed theme-dark'} shadow`}
data-cy="popover-card"
>
<Popover.Content>
<Popover.Body>
<div className="row">
<div className="col-3 p-2">
<span data-cy="event-label">{t('editor.inspector.eventManager.event', 'Event')}</span>
@ -692,7 +692,7 @@ export const EventManager = ({
</>
)}
</div>
</Popover.Content>
</Popover.Body>
</Popover>
);
}

View file

@ -1,6 +1,7 @@
import React, { useState } from 'react';
import SelectSearch, { fuzzySearch } from 'react-select-search';
import SelectSearch from 'react-select-search';
import Collapse from 'react-bootstrap/Collapse';
// eslint-disable-next-line import/no-unresolved
import { useTranslation } from 'react-i18next';
export const QuerySelector = ({ param, definition, eventOptionUpdated, dataQueries, extraData, eventMeta }) => {
@ -43,7 +44,7 @@ export const QuerySelector = ({ param, definition, eventOptionUpdated, dataQueri
onChange={(value) => {
onChange(value);
}}
filterOptions={fuzzySearch}
fuzzySearch
placeholder={t('globals.select', 'Select') + '...'}
/>
</div>

View file

@ -14,7 +14,7 @@ export const GlobalSettings = ({ darkMode, showHideViewerNavigationControls, sho
rootClose={true}
overlay={
<Popover id="page-handler-menu" className={`global-settings ${darkMode && 'popover-dark-themed'}`}>
<Popover.Content bsPrefix="popover-body">
<Popover.Body bsPrefix="popover-body">
<div className="card-body">
<label htmlFor="pin" className="form-label" data-cy={`page-settings-header`}>
Settings
@ -24,11 +24,13 @@ export const GlobalSettings = ({ darkMode, showHideViewerNavigationControls, sho
<Toggle onChange={onChange} value={!showPageViwerPageNavitation} />
</div>
</div>
</Popover.Content>
</Popover.Body>
</Popover>
}
>
<MenuIcon width="10" height="16" data-cy={'menu-icon'} />
<span>
<MenuIcon width="10" height="16" data-cy={'menu-icon'} />
</span>
</OverlayTrigger>
);
};

View file

@ -29,7 +29,7 @@ export const PagehandlerMenu = ({ page, darkMode, handlePageCallback, showMenu,
show={showMenu}
overlay={
<Popover key={page.id} id="page-handler-menu" className={darkMode && 'popover-dark-themed'}>
<Popover.Content key={page.id} bsPrefix="popover-body">
<Popover.Body key={page.id} bsPrefix="popover-body">
<div className="card-body">
<PageHandleField page={page} updatePageHandle={handlePageCallback} />
<hr style={{ margin: '0.75rem 0' }} />
@ -84,19 +84,21 @@ export const PagehandlerMenu = ({ page, darkMode, handlePageCallback, showMenu,
/>
</div>
</div>
</Popover.Content>
</Popover.Body>
</Popover>
}
>
<Button.UnstyledButton
onClick={(event) => {
event.stopPropagation();
setShowMenu(true);
}}
styles={{ height: '20px', marginTop: '2px' }}
>
<Button.Content dataCy={`page-menu`} iconSrc={'assets/images/icons/3dots-menu.svg'} />
</Button.UnstyledButton>
<span>
<Button.UnstyledButton
onClick={(event) => {
event.stopPropagation();
setShowMenu(true);
}}
styles={{ height: '20px', marginTop: '2px' }}
>
<Button.Content dataCy={`page-menu`} iconSrc={'assets/images/icons/3dots-menu.svg'} />
</Button.UnstyledButton>
</span>
</OverlayTrigger>
);
};

View file

@ -63,7 +63,7 @@ const LeftSidebarPageSelector = ({
const popoverContent = (
<div>
<div className="card-body p-0 pb-5" onClick={(event) => event.stopPropagation()}>
<div className="card-body p-0 pb-5">
<HeaderSection darkMode={darkMode}>
<HeaderSection.PanelHeader
title="Pages"

View file

@ -1,7 +1,7 @@
import React from 'react';
import { dataqueryService } from '@/_services';
import { toast } from 'react-hot-toast';
import ReactTooltip from 'react-tooltip';
import { Tooltip } from 'react-tooltip';
import { allSources, source } from './QueryEditors';
import { Transformation } from './Transformation';
import { previewQuery } from '@/_helpers/appUtils';
@ -12,6 +12,7 @@ import Preview from './Preview';
import DataSourceLister from './DataSourceLister';
import _, { isEmpty, isEqual, capitalize } from 'lodash';
import { allOperations } from '@tooljet/plugins/client';
// eslint-disable-next-line import/no-unresolved
import { withTranslation } from 'react-i18next';
import cx from 'classnames';
// eslint-disable-next-line import/no-unresolved
@ -54,7 +55,6 @@ class QueryManagerComponent extends React.Component {
}
setStateFromProps = (props) => {
console.log('setStateFromProps--- ', props.isUnsavedQueriesAvailable);
const selectedQuery = props.selectedQuery;
const dataSourceId = selectedQuery?.data_source_id;
@ -522,8 +522,6 @@ class QueryManagerComponent extends React.Component {
})}
key={selectedQuery ? selectedQuery.id : ''}
>
<ReactTooltip type="dark" effect="solid" delayShow={250} />
<div className="row header" style={{ padding: '8px 0' }}>
<div className="col d-flex align-items-center px-3 h-100 font-weight-500 py-1" style={{ gap: '10px' }}>
{(addingQuery || editingQuery) && selectedDataSource && (
@ -700,7 +698,8 @@ class QueryManagerComponent extends React.Component {
<span
onClick={this.props.toggleQueryEditor}
className={`cursor-pointer m-3 toggle-query-editor-svg d-flex`}
data-tip="Hide query editor"
data-tooltip-id="tooltip-for-hide-query-editor"
data-tooltip-content="Hide query editor"
>
<svg width="5.58" height="10.25" viewBox="0 0 6 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -709,6 +708,7 @@ class QueryManagerComponent extends React.Component {
/>
</svg>
</span>
<Tooltip id="tooltip-for-hide-query-editor" className="tooltip" />
</div>
</div>

View file

@ -1,5 +1,6 @@
import React, { useState, useRef, useCallback, useEffect } from 'react';
import { useEventListener } from '@/_hooks/use-event-listener';
import { Tooltip } from 'react-tooltip';
const QueryPanel = ({ children, computeCurrentQueryPanelHeight }) => {
const queryManagerPreferences = useRef(JSON.parse(localStorage.getItem('queryManagerPreferences')) ?? {});
@ -93,7 +94,12 @@ const QueryPanel = ({ children, computeCurrentQueryPanelHeight }) => {
<h5 className="mb-0 font-weight-500 cursor-pointer" onClick={toggleQueryEditor}>
QUERIES
</h5>
<span onClick={toggleQueryEditor} className="cursor-pointer m-1 d-flex" data-tip="Show query editor">
<span
onClick={toggleQueryEditor}
className="cursor-pointer m-1 d-flex"
data-tooltip-id="tooltip-for-show-query-editor"
data-tooltip-content="Show query editor"
>
{isExpanded ? (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -128,6 +134,7 @@ const QueryPanel = ({ children, computeCurrentQueryPanelHeight }) => {
setCancelData,
})}
</div>
<Tooltip id="tooltip-for-show-query-editor" className="tooltip" />
</>
);
};

View file

@ -1,5 +1,4 @@
import React from 'react';
import ReactTooltip from 'react-tooltip';
import Popover from '@/_ui/Popover';
import Avatar from '@/_ui/Avatar';
// eslint-disable-next-line import/no-unresolved
@ -18,13 +17,6 @@ const RealtimeAvatars = ({ darkMode }) => {
const getAvatarText = (presence) => presence.firstName?.charAt(0) + presence.lastName?.charAt(0);
const getAvatarTitle = (presence) => `${presence.firstName} ${presence.lastName}`;
// This is required for the tooltip not binding to dynamic content
// i.e. when others on the same version changes, tooltip
// ref: https://github.com/wwayne/react-tooltip#3-tooltip-not-binding-to-dynamic-content
React.useEffect(() => {
ReactTooltip.rebuild();
}, [othersOnSameVersionAndPage?.length]);
const popoverContent = () => {
return othersOnSameVersionAndPage
.slice(MAX_DISPLAY_USERS, othersOnSameVersionAndPage.length)
@ -40,6 +32,7 @@ const RealtimeAvatars = ({ darkMode }) => {
text={getAvatarText(presence)}
image={presence?.image}
borderShape="rounded"
indexId={id}
/>
</div>
<div className={`col text-truncate ${darkMode && 'text-white'}`}>
@ -72,6 +65,7 @@ const RealtimeAvatars = ({ darkMode }) => {
text={getAvatarText(self?.presence)}
image={self?.presence?.image}
borderShape="rounded"
indexId={self?.presence?.id}
/>
)}
{othersOnSameVersionAndPage.slice(0, MAX_DISPLAY_USERS).map(({ id, presence }) => {
@ -83,6 +77,7 @@ const RealtimeAvatars = ({ darkMode }) => {
text={getAvatarText(presence)}
image={presence?.image}
borderShape="rounded"
indexId={id}
/>
);
})}

View file

@ -5,6 +5,7 @@ import { RoomProvider } from '@y-presence/react';
import Spinner from '@/_ui/Spinner';
import { Editor } from '@/Editor';
import useRouter from '@/_hooks/use-router';
import { useParams } from 'react-router-dom';
const Y = require('yjs');
const psl = require('psl');
const { WebsocketProvider } = require('y-websocket');
@ -27,7 +28,8 @@ const getWebsocketUrl = () => {
};
export const RealtimeEditor = (props) => {
const appId = props.match.params.id;
const params = useParams();
const appId = params.id;
const [provider, setProvider] = React.useState();
const router = useRouter();

View file

@ -74,15 +74,18 @@ export const SubContainer = ({
allComponents[parent]?.component?.component === 'Form') ??
false;
let childWidgets = [];
Object.keys(allComponents).forEach((key) => {
if (allComponents[key].parent === parent) {
childWidgets[key] = { ...allComponents[key], component: { ...allComponents[key]['component'], parent } };
}
});
const getChildWidgets = (components) => {
let childWidgets = [];
Object.keys(components).forEach((key) => {
if (components[key].parent === parent) {
childWidgets[key] = { ...components[key], component: { ...components[key]['component'], parent } };
}
});
return childWidgets;
};
const [boxes, setBoxes] = useState(allComponents);
const [childWidgets, setChildWidgets] = useState(() => getChildWidgets(allComponents));
const [isDragging, setIsDragging] = useState(false);
const [isResizing, setIsResizing] = useState(false);
// const [subContainerHeight, setSubContainerHeight] = useState('100%'); //used to determine the height of the sub container for modal
@ -90,7 +93,9 @@ export const SubContainer = ({
useEffect(() => {
setBoxes(allComponents);
}, [allComponents]);
setChildWidgets(() => getChildWidgets(allComponents));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [allComponents, parent]);
useEffect(() => {
if (mounted) {
@ -348,6 +353,7 @@ export const SubContainer = ({
}
}
setChildWidgets(() => getChildWidgets(newBoxes));
setBoxes(newBoxes);
}
@ -534,18 +540,6 @@ export const SubContainer = ({
);
}
})}
{Object.keys(boxes).length === 0 && !appLoading && !isDragging && (
<div className="mx-auto mt-5 w-50 p-5 bg-light no-components-box" data-cy="----Test----">
<center className="text-muted">
Drag components from the right sidebar and drop here. Check out our{' '}
<a href="https://docs.tooljet.io/docs/tutorial/adding-widget" target="_blank" rel="noreferrer">
guide
</a>{' '}
on adding widgets.
</center>
</div>
)}
{appLoading && (
<div className="mx-auto mt-5 w-50 p-5">
<center>

View file

@ -20,9 +20,10 @@ import { DataSourceTypes } from './DataSourceManager/SourceComponents';
import { resolveReferences, safelyParseJSON, stripTrailingSlash } from '@/_helpers/utils';
import { withTranslation } from 'react-i18next';
import _ from 'lodash';
import { Redirect } from 'react-router-dom';
import { Navigate } from 'react-router-dom';
import Spinner from '@/_ui/Spinner';
import { toast } from 'react-hot-toast';
import { withRouter } from '@/_hoc/withRouter';
class ViewerComponent extends React.Component {
constructor(props) {
@ -31,11 +32,11 @@ class ViewerComponent extends React.Component {
const deviceWindowWidth = window.screen.width - 5;
const isMobileDevice = deviceWindowWidth < 600;
const pageHandle = this.props.match?.params?.pageHandle;
const pageHandle = this.props?.params?.pageHandle;
const slug = this.props.match.params.slug;
const appId = this.props.match.params.id;
const versionId = this.props.match.params.versionId;
const slug = this.props.params.slug;
const appId = this.props.params.id;
const versionId = this.props.params.versionId;
this.state = {
slug,
@ -128,7 +129,7 @@ class ViewerComponent extends React.Component {
const pages = Object.entries(data.definition.pages).map(([pageId, page]) => ({ id: pageId, ...page }));
const homePageId = data.definition.homePageId;
const startingPageHandle = this.props.match?.params?.pageHandle;
const startingPageHandle = this.props?.params?.pageHandle;
const currentPageId = pages.filter((page) => page.handle === startingPageHandle)[0]?.id ?? homePageId;
const currentPage = pages.find((page) => page.id === currentPageId);
@ -169,6 +170,7 @@ class ViewerComponent extends React.Component {
this.setState({ initialComputationOfStateDone: true });
console.log('Default component state computed and set');
this.runQueries(data.data_queries);
// eslint-disable-next-line no-unsafe-optional-chaining
const { events } = this.state.appDefinition?.pages[this.state.currentPageId];
for (const event of events ?? []) {
await this.handleEvent(event.eventId, event);
@ -270,41 +272,41 @@ class ViewerComponent extends React.Component {
this.switchOrganization(errorObj?.organizationId, appId, versionId);
return;
}
return <Redirect to={'/'} />;
return <Navigate replace to={'/'} />;
} else if (statusCode === 401) {
return <Redirect to={`/login?redirectTo=${this.props.location.pathname}`} />;
return <Navigate replace to={`/login?redirectTo=${this.props.location.pathname}`} />;
} else if (statusCode === 404) {
toast.error(errorDetails?.error ?? 'App not found', {
position: 'top-center',
});
}
return <Redirect to={'/'} />;
return <Navigate replace to={'/'} />;
}
} catch (err) {
return <Redirect to={'/'} />;
return <Navigate replace to={'/'} />;
}
};
componentDidMount() {
const slug = this.props.match.params.slug;
const appId = this.props.match.params.id;
const versionId = this.props.match.params.versionId;
const slug = this.props.params.slug;
const appId = this.props.params.id;
const versionId = this.props.params.versionId;
this.setState({ isLoading: false });
slug ? this.loadApplicationBySlug(slug) : this.loadApplicationByVersion(appId, versionId);
}
componentDidUpdate(prevProps) {
if (this.props.match.params.slug && this.props.match.params.slug !== prevProps.match.params.slug) {
if (this.props.params.slug && this.props.params.slug !== prevProps.params.slug) {
this.setState({ isLoading: true });
this.loadApplicationBySlug(this.props.match.params.slug);
this.loadApplicationBySlug(this.props.params.slug);
}
if (this.state.initialComputationOfStateDone) this.handlePageSwitchingBasedOnURLparam();
}
handlePageSwitchingBasedOnURLparam() {
const handleOnURL = this.props.match.params.pageHandle;
const handleOnURL = this.props.params.pageHandle;
const pageIdCorrespondingToHandleOnURL = handleOnURL
? this.findPageIdFromHandle(handleOnURL)
: this.state.appDefinition.homePageId;
@ -344,6 +346,7 @@ class ViewerComponent extends React.Component {
async () => {
computeComponentState(this, this.state.appDefinition?.pages[this.state.currentPageId].components).then(
async () => {
// eslint-disable-next-line no-unsafe-optional-chaining
const { events } = this.state.appDefinition?.pages[this.state.currentPageId];
for (const event of events ?? []) {
await this.handleEvent(event.eventId, event);
@ -404,9 +407,9 @@ class ViewerComponent extends React.Component {
const queryParamsString = queryParams.map(([key, value]) => `${key}=${value}`).join('&');
if (this.state.slug) this.props.history.push(`/applications/${this.state.slug}/${handle}?${queryParamsString}`);
if (this.state.slug) this.props.navigate(`/applications/${this.state.slug}/${handle}?${queryParamsString}`);
else
this.props.history.push(
this.props.navigate(
`/applications/${this.state.appId}/versions/${this.state.versionId}/${handle}?${queryParamsString}`
);
};
@ -574,4 +577,4 @@ class ViewerComponent extends React.Component {
}
}
export const Viewer = withTranslation()(ViewerComponent);
export const Viewer = withTranslation()(withRouter(ViewerComponent));

View file

@ -1,13 +1,12 @@
import React, { useState, useCallback, useEffect } from 'react';
import cx from 'classnames';
import { AppMenu } from './AppMenu';
import { history } from '@/_helpers';
import moment from 'moment';
import { ToolTip } from '@/_components';
import { Fade } from '@/_ui/Fade';
import useHover from '@/_hooks/useHover';
import configs from './Configs/AppIcon.json';
import { Link } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
import urlJoin from 'url-join';
import { useTranslation } from 'react-i18next';
const { defaultIcon } = configs;
@ -28,6 +27,7 @@ export default function AppCard({
const [focused, setFocused] = useState(false);
const [isMenuOpen, setMenuOpen] = useState(false);
const { t } = useTranslation();
const navigate = useNavigate();
const onMenuToggle = useCallback(
(status) => {
@ -159,7 +159,7 @@ export default function AppCard({
if (app?.current_version_id) {
window.open(urlJoin(window.public_config?.TOOLJET_HOST, `/applications/${app.slug}`));
} else {
history.push(app?.current_version_id ? `/applications/${app.slug}` : '');
navigate(app?.current_version_id ? `/applications/${app.slug}` : '');
}
}}
data-cy="launch-button"

View file

@ -45,7 +45,7 @@ export const AppMenu = function AppMenu({
onToggle={onMenuOpen}
overlay={
<Popover id="popover-app-menu" className={darkMode && 'popover-dark-themed'}>
<Popover.Content bsPrefix="popover-body">
<Popover.Body bsPrefix="popover-body">
<div data-cy="card-options">
{canUpdateApp && (
<Field
@ -78,7 +78,7 @@ export const AppMenu = function AppMenu({
/>
)}
</div>
</Popover.Content>
</Popover.Body>
</Popover>
}
>

View file

@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { toast } from 'react-hot-toast';
import TemplateLibraryModal from './TemplateLibraryModal/';
import { useHistory } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { libraryAppService } from '@/_services';
import EmptyIllustration from '@assets/images/no-apps.svg';
import { useNavigate } from 'react-router-dom';
export const BlankPage = function BlankPage({
createApp,
@ -19,7 +19,7 @@ export const BlankPage = function BlankPage({
}) {
const { t } = useTranslation();
const [deploying, setDeploying] = useState(false);
const history = useHistory();
const navigate = useNavigate();
const staticTemplates = [
{ id: 's3-file-explorer', name: 'S3 File Explorer' },
@ -37,7 +37,7 @@ export const BlankPage = function BlankPage({
setDeploying(false);
toast.dismiss(loadingToastId);
toast.success('App created.');
history.push(`/apps/${data.id}`);
navigate(`/apps/${data.id}`);
})
.catch((e) => {
toast.dismiss(loadingToastId);

View file

@ -45,8 +45,13 @@ export const FolderMenu = function FolderMenu({
setOpen(isOpen);
}}
overlay={
<Popover id="popover-app-menu" className={darkMode && 'popover-dark-themed'} data-cy="folder-card">
<Popover.Content bsPrefix="popover-body">
<Popover
id="popover-app-menu"
className={darkMode && 'popover-dark-themed'}
data-cy="folder-card"
style={{ transition: 'none' }}
>
<Popover.Body bsPrefix="popover-body">
<div>
{canUpdateFolder && (
<Field text={t('homePage.foldersSection.editFolder', 'Edit folder')} onClick={editFolder} />
@ -59,7 +64,7 @@ export const FolderMenu = function FolderMenu({
/>
)}
</div>
</Popover.Content>
</Popover.Body>
</Popover>
}
>

View file

@ -17,6 +17,7 @@ import { withTranslation } from 'react-i18next';
import { sample } from 'lodash';
import ExportAppModal from './ExportAppModal';
import Footer from './Footer';
import { withRouter } from '@/_hoc/withRouter';
const { iconList, defaultIcon } = configs;
@ -116,7 +117,7 @@ class HomePageComponent extends React.Component {
appService
.createApp({ icon: sample(iconList) })
.then((data) => {
_self.props.history.push(`/apps/${data.id}`);
_self.props.navigate(`/apps/${data.id}`);
})
.catch(({ error }) => {
toast.error(error);
@ -135,7 +136,7 @@ class HomePageComponent extends React.Component {
.then((data) => {
toast.success('App cloned successfully.');
this.setState({ isCloningApp: false });
this.props.history.push(`/apps/${data.id}`);
this.props.navigate(`/apps/${data.id}`);
})
.catch(({ _error }) => {
toast.error('Could not clone the app.');
@ -163,7 +164,7 @@ class HomePageComponent extends React.Component {
this.setState({
isImportingApp: false,
});
this.props.history.push(`/apps/${data.id}`);
this.props.navigate(`/apps/${data.id}`);
})
.catch(({ error }) => {
toast.error(`Could not import the app: ${error}`);
@ -687,4 +688,4 @@ class HomePageComponent extends React.Component {
}
}
export const HomePage = withTranslation()(HomePageComponent);
export const HomePage = withTranslation()(withRouter(HomePageComponent));

View file

@ -57,8 +57,10 @@ const SearchBoxContainer = ({ onChange, queryString }) => {
}
return () => {
document.querySelector('.template-search-box .input-icon .form-control:not(:first-child)').style.paddingLeft =
'2.5rem';
if (document.querySelector('.template-search-box .input-icon .form-control:not(:first-child)')) {
document.querySelector('.template-search-box .input-icon .form-control:not(:first-child)').style.paddingLeft =
'2.5rem';
}
};
}, [searchText]);

View file

@ -16,8 +16,8 @@ export default function TemplateDisplay(props) {
{sources?.map((source) => (
<Badge
className="me-2"
variant="primary"
key={source.id}
bg={props.darkMode ? 'dark' : 'light'}
style={{
backgroundColor: '#D2DDEC',
color: 'black',

View file

@ -6,7 +6,7 @@ import { libraryAppService } from '@/_services';
import { toast } from 'react-hot-toast';
import _ from 'lodash';
import TemplateDisplay from './TemplateDisplay';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
const identifyUniqueCategories = (templates) =>
@ -16,7 +16,7 @@ const identifyUniqueCategories = (templates) =>
}));
export default function TemplateLibraryModal(props) {
const history = useHistory();
const navigate = useNavigate();
const [libraryApps, setLibraryApps] = useState([]);
const [selectedCategory, selectCategory] = useState({ id: 'all', count: 0 });
const filteredApps = libraryApps.filter(
@ -60,7 +60,7 @@ export default function TemplateLibraryModal(props) {
toast.success('App created.', {
position: 'top-center',
});
history.push(`/apps/${data.id}`);
navigate(`/apps/${data.id}`);
})
.catch((e) => {
toast.error(e.error, {

View file

@ -1,7 +1,7 @@
import React from 'react';
import { authenticationService } from '@/_services';
import { toast } from 'react-hot-toast';
import { Link } from 'react-router-dom';
import { Link, Navigate } from 'react-router-dom';
import queryString from 'query-string';
import GoogleSSOLoginButton from '@ee/components/LoginPage/GoogleSSOLoginButton';
import GitSSOLoginButton from '@ee/components/LoginPage/GitSSOLoginButton';
@ -15,6 +15,7 @@ import EyeHide from '../../assets/images/onboardingassets/Icons/EyeHide';
import EyeShow from '../../assets/images/onboardingassets/Icons/EyeShow';
import Spinner from '@/_ui/Spinner';
import { getCookie, eraseCookie, setCookie } from '@/_helpers/cookie';
import { withRouter } from '@/_hoc/withRouter';
class LoginPageComponent extends React.Component {
constructor(props) {
super(props);
@ -24,9 +25,10 @@ class LoginPageComponent extends React.Component {
isGettingConfigs: true,
configs: undefined,
emailError: false,
navigateToLogin: false,
};
this.single_organization = window.public_config?.DISABLE_MULTI_WORKSPACE === 'true';
this.organizationId = props.match.params.organizationId;
this.organizationId = props.params.organizationId;
}
darkMode = localStorage.getItem('darkMode') === 'true';
@ -37,10 +39,7 @@ class LoginPageComponent extends React.Component {
(!this.organizationId && authenticationService.currentUserValue) ||
(this.organizationId && authenticationService?.currentUserValue?.organization_id === this.organizationId)
) {
// redirect to home if already logged in
// set redirect path for sso login
const redirectPath = this.eraseRedirectUrl();
return this.props.history.push(redirectPath ? redirectPath : '/');
return this.setState({ navigateToLogin: true });
}
if (this.organizationId || this.single_organization)
authenticationService.saveLoginOrganizationId(this.organizationId);
@ -51,7 +50,7 @@ class LoginPageComponent extends React.Component {
},
(response) => {
if (response.data.statusCode !== 404) {
return this.props.history.push({
return this.props.navigate({
pathname: '/',
state: { errorMessage: 'Error while login, please try again' },
});
@ -59,7 +58,7 @@ class LoginPageComponent extends React.Component {
// If there is no organization found for single organization setup
// show form to sign up
// redirected here for self hosted version
this.props.history.push('/setup');
this.props.navigate('/setup');
this.setState({
isGettingConfigs: false,
@ -131,7 +130,7 @@ class LoginPageComponent extends React.Component {
const params = queryString.parse(this.props.location.search);
const { from } = params.redirectTo ? { from: { pathname: params.redirectTo } } : { from: { pathname: '/' } };
const redirectPath = from.pathname === '/confirm' ? '/' : from;
this.props.history.push(redirectPath);
this.props.navigate(redirectPath);
this.setState({ isLoading: false });
this.eraseRedirectUrl();
};
@ -144,227 +143,238 @@ class LoginPageComponent extends React.Component {
this.setState({ isLoading: false });
};
redirectToUrl = () => {
const redirectPath = this.eraseRedirectUrl();
return redirectPath ? redirectPath : '/';
};
render() {
const { isLoading, configs, isGettingConfigs } = this.state;
const { isLoading, configs, isGettingConfigs, navigateToLogin } = this.state;
return (
<>
<div className="common-auth-section-whole-wrapper page">
<div className="common-auth-section-left-wrapper">
<OnboardingNavbar darkMode={this.darkMode} />
<div className="common-auth-section-left-wrapper-grid">
{this.state.isGettingConfigs && (
<div className="loader-wrapper">
<ShowLoading />
</div>
)}
<form action="." method="get" autoComplete="off">
{isGettingConfigs ? (
{navigateToLogin ? (
<Navigate to={this.redirectToUrl()} />
) : (
<div className="common-auth-section-whole-wrapper page">
<div className="common-auth-section-left-wrapper">
<OnboardingNavbar darkMode={this.darkMode} />
<div className="common-auth-section-left-wrapper-grid">
{this.state.isGettingConfigs && (
<div className="loader-wrapper">
<ShowLoading />
</div>
) : (
<div className="common-auth-container-wrapper ">
{!configs?.form && !configs?.git && !configs?.google && (
<div className="text-center-onboard">
<h2 data-cy="no-login-methods-warning">
{this.props.t(
'loginSignupPage.noLoginMethodsEnabled',
'No login methods enabled for this workspace'
)}
</h2>
</div>
)}
<div>
{(this.state?.configs?.google?.enabled ||
this.state?.configs?.git?.enabled ||
configs?.form?.enabled) && (
<>
<h2 className="common-auth-section-header sign-in-header" data-cy="sign-in-header">
{this.props.t('loginSignupPage.signIn', `Sign in`)}
</h2>
{this.organizationId && (
<p
className="text-center-onboard workspace-login-description"
data-cy="workspace-sign-in-sub-header"
>
Sign in to your workspace - {configs?.name}
</p>
)}
<div className="tj-text-input-label">
{!this.organizationId && (configs?.form?.enable_sign_up || configs?.enable_sign_up) && (
<div className="common-auth-sub-header sign-in-sub-header" data-cy="sign-in-sub-header">
{this.props.t('newToTooljet', 'New to ToolJet?')}
<Link
to={'/signup'}
tabIndex="-1"
style={{ marginLeft: '4px' }}
data-cy="create-an-account-link"
>
{this.props.t('loginSignupPage.createToolJetAccount', `Create an account`)}
</Link>
</div>
)}
<form action="." method="get" autoComplete="off">
{isGettingConfigs ? (
<div className="loader-wrapper">
<ShowLoading />
</div>
) : (
<div className="common-auth-container-wrapper ">
{!configs?.form && !configs?.git && !configs?.google && (
<div className="text-center-onboard">
<h2 data-cy="no-login-methods-warning">
{this.props.t(
'loginSignupPage.noLoginMethodsEnabled',
'No login methods enabled for this workspace'
)}
</div>
</>
)}
{this.state?.configs?.git?.enabled && (
<div className="login-sso-wrapper">
<GitSSOLoginButton configs={this.state?.configs?.git?.configs} />
</h2>
</div>
)}
{this.state?.configs?.google?.enabled && (
<div className="login-sso-wrapper">
<GoogleSSOLoginButton
configs={this.state?.configs?.google?.configs}
configId={this.state?.configs?.google?.config_id}
/>
</div>
)}
{(this.state?.configs?.google?.enabled || this.state?.configs?.git?.enabled) &&
configs?.form?.enabled && (
<div className="separator-onboarding ">
<div className="mt-2 separator" data-cy="onboarding-separator">
<h2>
<span>OR</span>
</h2>
<div>
{(this.state?.configs?.google?.enabled ||
this.state?.configs?.git?.enabled ||
configs?.form?.enabled) && (
<>
<h2 className="common-auth-section-header sign-in-header" data-cy="sign-in-header">
{this.props.t('loginSignupPage.signIn', `Sign in`)}
</h2>
{this.organizationId && (
<p
className="text-center-onboard workspace-login-description"
data-cy="workspace-sign-in-sub-header"
>
Sign in to your workspace - {configs?.name}
</p>
)}
<div className="tj-text-input-label">
{!this.organizationId && (configs?.form?.enable_sign_up || configs?.enable_sign_up) && (
<div className="common-auth-sub-header sign-in-sub-header" data-cy="sign-in-sub-header">
{this.props.t('newToTooljet', 'New to ToolJet?')}
<Link
to={'/signup'}
tabIndex="-1"
style={{ marginLeft: '4px' }}
data-cy="create-an-account-link"
>
{this.props.t('loginSignupPage.createToolJetAccount', `Create an account`)}
</Link>
</div>
)}
</div>
</>
)}
{this.state?.configs?.git?.enabled && (
<div className="login-sso-wrapper">
<GitSSOLoginButton configs={this.state?.configs?.git?.configs} />
</div>
)}
{configs?.form?.enabled && (
<>
<div className="signin-email-wrap">
<label className="tj-text-input-label" data-cy="work-email-label">
{this.props.t('loginSignupPage.workEmail', 'Email?')}
</label>
<input
onChange={this.handleChange}
name="email"
type="email"
className="tj-text-input"
placeholder={this.props.t('loginSignupPage.enterWorkEmail', 'Enter your email')}
style={{ marginBottom: '0px' }}
data-cy="work-email-input"
autoFocus
autoComplete="off"
{this.state?.configs?.google?.enabled && (
<div className="login-sso-wrapper">
<GoogleSSOLoginButton
configs={this.state?.configs?.google?.configs}
configId={this.state?.configs?.google?.config_id}
/>
{this.state?.emailError && (
<span className="tj-text-input-error-state" data-cy="email-error-message">
{this.state?.emailError}
</span>
)}
</div>
<div>
<label className="tj-text-input-label" data-cy="password-label">
{this.props.t('loginSignupPage.password', 'Password')}
<span style={{ marginLeft: '4px' }}>
<Link
to={'/forgot-password'}
tabIndex="-1"
className="login-forgot-password"
style={{ color: this.darkMode && '#3E63DD' }}
data-cy="forgot-password-link"
>
{this.props.t('loginSignupPage.forgot', 'Forgot?')}
</Link>
</span>
</label>
<div className="login-password">
<input
onChange={this.handleChange}
name="password"
type={this.state?.showPassword ? 'text' : 'password'}
className="tj-text-input"
placeholder={this.props.t('loginSignupPage.EnterPassword', 'Enter password')}
data-cy="password-input-field"
autoComplete="new-password"
/>
<div
className="login-password-hide-img"
onClick={this.handleOnCheck}
data-cy="show-password-icon"
>
{this.state?.showPassword ? (
<EyeHide
fill={
this.darkMode
? this.state?.password?.length
? '#D1D5DB'
: '#656565'
: this.state?.password?.length
? '#384151'
: '#D1D5DB'
}
/>
) : (
<EyeShow
fill={
this.darkMode
? this.state?.password?.length
? '#D1D5DB'
: '#656565'
: this.state?.password?.length
? '#384151'
: '#D1D5DB'
}
/>
)}
)}
{(this.state?.configs?.google?.enabled || this.state?.configs?.git?.enabled) &&
configs?.form?.enabled && (
<div className="separator-onboarding ">
<div className="mt-2 separator" data-cy="onboarding-separator">
<h2>
<span>OR</span>
</h2>
</div>
</div>
</div>
</>
)}
</div>
<div className={` d-flex flex-column align-items-center ${!configs?.form?.enabled ? 'mt-0' : ''}`}>
{configs?.form?.enabled && (
<ButtonSolid
className="login-btn"
onClick={this.authUser}
disabled={isLoading}
data-cy="login-button"
>
{isLoading ? (
<div className="spinner-center">
<Spinner />
</div>
) : (
<>
<span> {this.props.t('loginSignupPage.loginTo', 'Login')}</span>
<EnterIcon
className="enter-icon-onboard"
fill={
isLoading || !this.state?.email || !this.state?.password
? this.darkMode
? '#656565'
: ' #D1D5DB'
: '#fff'
}
></EnterIcon>
</>
)}
</ButtonSolid>
)}
{authenticationService?.currentUserValue?.organization && this.organizationId && (
<div
className="text-center-onboard mt-3"
data-cy={`back-to-${String(authenticationService?.currentUserValue?.organization)
.toLowerCase()
.replace(/\s+/g, '-')}`}
>
back to&nbsp; <Link to="/">{authenticationService?.currentUserValue?.organization}</Link>
</div>
)}
{configs?.form?.enabled && (
<>
<div className="signin-email-wrap">
<label className="tj-text-input-label" data-cy="work-email-label">
{this.props.t('loginSignupPage.workEmail', 'Email?')}
</label>
<input
onChange={this.handleChange}
name="email"
type="email"
className="tj-text-input"
placeholder={this.props.t('loginSignupPage.enterWorkEmail', 'Enter your email')}
style={{ marginBottom: '0px' }}
data-cy="work-email-input"
autoFocus
autoComplete="off"
/>
{this.state?.emailError && (
<span className="tj-text-input-error-state" data-cy="email-error-message">
{this.state?.emailError}
</span>
)}
</div>
<div>
<label className="tj-text-input-label" data-cy="password-label">
{this.props.t('loginSignupPage.password', 'Password')}
<span style={{ marginLeft: '4px' }}>
<Link
to={'/forgot-password'}
tabIndex="-1"
className="login-forgot-password"
style={{ color: this.darkMode && '#3E63DD' }}
data-cy="forgot-password-link"
>
{this.props.t('loginSignupPage.forgot', 'Forgot?')}
</Link>
</span>
</label>
<div className="login-password">
<input
onChange={this.handleChange}
name="password"
type={this.state?.showPassword ? 'text' : 'password'}
className="tj-text-input"
placeholder={this.props.t('loginSignupPage.EnterPassword', 'Enter password')}
data-cy="password-input-field"
autoComplete="new-password"
/>
<div
className="login-password-hide-img"
onClick={this.handleOnCheck}
data-cy="show-password-icon"
>
{this.state?.showPassword ? (
<EyeHide
fill={
this.darkMode
? this.state?.password?.length
? '#D1D5DB'
: '#656565'
: this.state?.password?.length
? '#384151'
: '#D1D5DB'
}
/>
) : (
<EyeShow
fill={
this.darkMode
? this.state?.password?.length
? '#D1D5DB'
: '#656565'
: this.state?.password?.length
? '#384151'
: '#D1D5DB'
}
/>
)}
</div>
</div>
</div>
</>
)}
</div>
<div
className={` d-flex flex-column align-items-center ${!configs?.form?.enabled ? 'mt-0' : ''}`}
>
{configs?.form?.enabled && (
<ButtonSolid
className="login-btn"
onClick={this.authUser}
disabled={isLoading}
data-cy="login-button"
>
{isLoading ? (
<div className="spinner-center">
<Spinner />
</div>
) : (
<>
<span> {this.props.t('loginSignupPage.loginTo', 'Login')}</span>
<EnterIcon
className="enter-icon-onboard"
fill={
isLoading || !this.state?.email || !this.state?.password
? this.darkMode
? '#656565'
: ' #D1D5DB'
: '#fff'
}
></EnterIcon>
</>
)}
</ButtonSolid>
)}
{authenticationService?.currentUserValue?.organization && this.organizationId && (
<div
className="text-center-onboard mt-3"
data-cy={`back-to-${String(authenticationService?.currentUserValue?.organization)
.toLowerCase()
.replace(/\s+/g, '-')}`}
>
back to&nbsp; <Link to="/">{authenticationService?.currentUserValue?.organization}</Link>
</div>
)}
</div>
</div>
</div>
)}
</form>
)}
</form>
</div>
</div>
</div>
</div>
)}
</>
);
}
}
export const LoginPage = withTranslation()(LoginPageComponent);
export const LoginPage = withTranslation()(withRouter(LoginPageComponent));

View file

@ -302,6 +302,8 @@ class ManageGroupPermissionResourcesComponent extends React.Component {
selectedUsers,
} = this.state;
const searchSelectClass = this.props.darkMode ? 'select-search-dark' : 'select-search';
const folder_permission = groupPermission
? groupPermission.folder_create && groupPermission.folder_delete && groupPermission.folder_update
: false;
@ -507,7 +509,23 @@ class ManageGroupPermissionResourcesComponent extends React.Component {
<div className="row">
<div className="col-6" data-cy="multi-select-search">
<MultiSelect
className={`${this.props.darkMode ? 'select-search-dark' : 'select-search'}`}
className={{
container: searchSelectClass,
value: `${searchSelectClass}__value`,
input: `${searchSelectClass}__input`,
select: `${searchSelectClass}__select`,
options: `${searchSelectClass}__options`,
row: `${searchSelectClass}__row`,
option: `${searchSelectClass}__option`,
group: `${searchSelectClass}__group`,
'group-header': `${searchSelectClass}__group-header`,
'is-selected': 'is-selected',
'is-highlighted': 'is-highlighted',
'is-loading': 'is-loading',
'is-multiple': 'is-multiple',
'has-focus': 'has-focus',
'not-found': `${searchSelectClass}__not-found`,
}}
onSelect={this.setSelectedUsers}
onSearch={(query) => this.searchUsersNotInGroup(query, groupPermission.id)}
selectedValues={selectedUsers}

View file

@ -1,7 +1,7 @@
import React from 'react';
import { authenticationService, organizationService, organizationUserService } from '@/_services';
import { toast } from 'react-hot-toast';
import ReactTooltip from 'react-tooltip';
// eslint-disable-next-line import/no-unresolved
import { withTranslation } from 'react-i18next';
import urlJoin from 'url-join';
import ErrorBoundary from '@/Editor/ErrorBoundary';
@ -246,8 +246,6 @@ class ManageOrgUsersComponent extends React.Component {
return (
<ErrorBoundary showFallback={true}>
<div className="wrapper org-users-page animation-fade">
<ReactTooltip type="dark" effect="solid" delayShow={250} />
<div className="page-wrapper">
<div className="container-xl">
<div className="page-header d-print-none">

View file

@ -2,9 +2,9 @@ import React from 'react';
import { authenticationService, orgEnvironmentVariableService } from '@/_services';
import { ConfirmDialog } from '@/_components';
import { toast } from 'react-hot-toast';
import ReactTooltip from 'react-tooltip';
import VariableForm from './VariableForm';
import VariablesTable from './VariablesTable';
// eslint-disable-next-line import/no-unresolved
import { withTranslation } from 'react-i18next';
class ManageOrgVarsComponent extends React.Component {
constructor(props) {
@ -236,8 +236,6 @@ class ManageOrgVarsComponent extends React.Component {
const { isLoading, showVariableForm, addingVar, variables } = this.state;
return (
<div className="wrapper org-variables-page animation-fade">
<ReactTooltip type="dark" effect="solid" delayShow={250} />
<ConfirmDialog
show={this.state.showVariableDeleteConfirmation}
message={this.props.t(

View file

@ -1,5 +1,6 @@
import React from 'react';
import { withTranslation } from 'react-i18next';
import { Tooltip } from 'react-tooltip';
class VariablesTable extends React.Component {
constructor(props) {
@ -122,48 +123,56 @@ class VariablesTable extends React.Component {
.replace(/\s+/g, '-')}-workspace-variable-update`}
>
{this.props.canUpdateVariable && (
<button
className="btn btn-sm btn-org-env"
onClick={() => this.props.onEditBtnClicked(variable)}
data-cy={`${variable.variable_name
.toLowerCase()
.replace(/\s+/g, '-')}-workspace-variable-edit-button`}
>
<div>
<img
data-tip="Update"
className="svg-icon"
src="assets/images/icons/edit.svg"
width="15"
height="15"
style={{
cursor: 'pointer',
}}
></img>
</div>
</button>
<>
<button
className="btn btn-sm btn-org-env"
onClick={() => this.props.onEditBtnClicked(variable)}
data-cy={`${variable.variable_name
.toLowerCase()
.replace(/\s+/g, '-')}-workspace-variable-edit-button`}
data-tooltip-id="tooltip-for-update"
data-tooltip-content="Update"
>
<div>
<img
className="svg-icon"
src="assets/images/icons/edit.svg"
width="15"
height="15"
style={{
cursor: 'pointer',
}}
></img>
</div>
</button>
<Tooltip id="tooltip-for-update" className="tooltip" />
</>
)}
{this.props.canDeleteVariable && (
<button
className="btn btn-sm btn-org-env"
onClick={() => this.props.onDeleteBtnClicked(variable)}
data-cy={`${variable.variable_name
.toLowerCase()
.replace(/\s+/g, '-')}-workspace-variable-delete-button`}
>
<div>
<img
data-tip="Delete"
className="svg-icon"
src="assets/images/icons/query-trash-icon.svg"
width="15"
height="15"
style={{
cursor: 'pointer',
}}
/>
</div>
</button>
<>
<button
className="btn btn-sm btn-org-env"
onClick={() => this.props.onDeleteBtnClicked(variable)}
data-cy={`${variable.variable_name
.toLowerCase()
.replace(/\s+/g, '-')}-workspace-variable-delete-button`}
data-tooltip-id="tooltip-for-delete"
data-tooltip-content="Delete"
>
<div>
<img
className="svg-icon"
src="assets/images/icons/query-trash-icon.svg"
width="15"
height="15"
style={{
cursor: 'pointer',
}}
/>
</div>
</button>
<Tooltip id="tooltip-for-delete" className="tooltip" />
</>
)}
</div>
</td>

View file

@ -1,12 +1,12 @@
import React, { useState, useCallback, useEffect } from 'react';
import { organizationService } from '@/_services';
import { Menu } from '@/_components';
import ReactTooltip from 'react-tooltip';
import { GeneralSettings } from './GeneralSettings';
import { Google } from './Google';
import { Loader } from './Loader';
import { Git } from './Git';
import { Form } from './Form';
// eslint-disable-next-line import/no-unresolved
import { useTranslation } from 'react-i18next';
import ErrorBoundary from '@/Editor/ErrorBoundary';
import { toast } from 'react-hot-toast';
@ -99,7 +99,6 @@ export function ManageSSO({ darkMode }) {
return (
<ErrorBoundary showFallback={true}>
<div className="wrapper manage-sso animation-fade">
<ReactTooltip type="dark" effect="solid" delayShow={250} />
<div className="page-wrapper">
<div className="container-xl">
<div className="page-header d-print-none">

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import useRouter from '@/_hooks/use-router';
import { authenticationService } from '@/_services';
import { Redirect } from 'react-router-dom';
import { Navigate } from 'react-router-dom';
import Configs from './Configs/Config.json';
import { RedirectLoader } from '../_components';
@ -59,7 +59,8 @@ export function Authorize() {
<div>
<RedirectLoader origin={Configs[router.query.origin] ? router.query.origin : 'unknown'} />
{(success || error) && (
<Redirect
<Navigate
replace
to={{
pathname: `/login${error && organizationId ? `/${organizationId}` : ''}`,
state: { errorMessage: error && error },

View file

@ -3,6 +3,7 @@ import queryString from 'query-string';
import { datasourceService } from '@/_services';
import { RedirectLoader } from '@/_components';
import { withTranslation } from 'react-i18next';
import { withRouter } from '@/_hoc/withRouter';
class AuthorizeComponent extends React.Component {
constructor(props) {
super(props);
@ -113,4 +114,4 @@ class AuthorizeComponent extends React.Component {
}
}
export const Authorize = withTranslation()(AuthorizeComponent);
export const Authorize = withTranslation()(withRouter(AuthorizeComponent));

View file

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { authenticationService } from '@/_services';
import { toast } from 'react-hot-toast';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import OnBoardingInput from './OnBoardingInput';
import OnBoardingRadioInput from './OnBoardingRadioInput';
import ContinueButton from './ContinueButton';
@ -13,7 +13,7 @@ import LogoDarkMode from '@assets/images/Logomark-dark-mode.svg';
function OnBoardingForm({ userDetails = {}, token = '', organizationToken = '', password, darkMode }) {
const Logo = darkMode ? LogoDarkMode : LogoLightMode;
const history = useHistory();
const navigate = useNavigate();
const [page, setPage] = useState(0);
const [completed, setCompleted] = useState(false);
const [isLoading, setIsLoading] = useState(false);
@ -48,7 +48,7 @@ function OnBoardingForm({ userDetails = {}, token = '', organizationToken = '',
authenticationService.updateUser(user);
authenticationService.deleteLoginOrganizationId();
setIsLoading(false);
history.push('/');
navigate('/');
})
.catch((res) => {
setIsLoading(false);

View file

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { authenticationService } from '@/_services';
import { toast } from 'react-hot-toast';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import OnBoardingInput from './OnBoardingInput';
import OnBoardingRadioInput from './OnBoardingRadioInput';
import AdminSetup from './AdminSetup';
@ -14,7 +14,7 @@ import LogoDarkMode from '@assets/images/Logomark-dark-mode.svg';
function OnbboardingFromSH({ darkMode }) {
const Logo = darkMode ? LogoDarkMode : LogoLightMode;
const history = useHistory();
const navigate = useNavigate();
const [page, setPage] = useState(0);
const [completed, setCompleted] = useState(false);
const [isLoading, setIsLoading] = useState(false);
@ -55,7 +55,7 @@ function OnbboardingFromSH({ darkMode }) {
authenticationService.updateUser(user);
authenticationService.deleteLoginOrganizationId();
setIsLoading(false);
history.push('/');
navigate('/');
})
.catch((res) => {
setIsLoading(false);

View file

@ -9,6 +9,7 @@ import EyeHide from '../../assets/images/onboardingassets/Icons/EyeHide';
import EyeShow from '../../assets/images/onboardingassets/Icons/EyeShow';
import { withTranslation } from 'react-i18next';
import Spinner from '@/_ui/Spinner';
import { withRouter } from '@/_hoc/withRouter';
class ResetPasswordComponent extends React.Component {
constructor(props) {
@ -39,7 +40,7 @@ class ResetPasswordComponent extends React.Component {
handleClick = (event) => {
event.preventDefault();
const { token } = this.props.location.state;
const { token } = this.props.params;
const { password, password_confirmation } = this.state;
if (password !== password_confirmation) {
@ -232,4 +233,4 @@ class ResetPasswordComponent extends React.Component {
}
}
export const ResetPassword = withTranslation()(ResetPasswordComponent);
export const ResetPassword = withTranslation()(withRouter(ResetPasswordComponent));

View file

@ -15,6 +15,7 @@ import { withTranslation } from 'react-i18next';
import { ShowLoading } from '@/_components';
import Spinner from '@/_ui/Spinner';
import SignupStatusCard from '../OnBoardingForm/SignupStatusCard';
import { withRouter } from '@/_hoc/withRouter';
class SignupPageComponent extends React.Component {
constructor(props) {
super(props);
@ -46,7 +47,7 @@ class SignupPageComponent extends React.Component {
if (response.data.statusCode !== 404) {
this.setState({ isGettingConfigs: false });
} else {
return this.props.history.push('/setup');
return this.props.navigate('/setup');
}
}
);
@ -330,4 +331,4 @@ class SignupPageComponent extends React.Component {
}
}
export const SignupPage = withTranslation()(SignupPageComponent);
export const SignupPage = withTranslation()(withRouter(SignupPageComponent));

View file

@ -1,6 +1,8 @@
import React from 'react';
import { ButtonSolid } from '@/_components/AppButton';
export const ForgotPasswordInfoScreen = function ForgotPasswordInfoScreen({ props, email, darkMode }) {
import { useNavigate } from 'react-router-dom';
export const ForgotPasswordInfoScreen = function ForgotPasswordInfoScreen({ email, darkMode }) {
const navigate = useNavigate();
return (
<div className="info-screen-wrapper">
<div className="forget-password-info-card">
@ -34,7 +36,7 @@ export const ForgotPasswordInfoScreen = function ForgotPasswordInfoScreen({ prop
<ButtonSolid
variant="secondary"
className="forgot-password-info-btn"
onClick={() => props.history.push('/login')}
onClick={() => navigate('/login')}
data-cy="back-to-login-button"
>
Back to log in

View file

@ -1,9 +1,9 @@
import React from 'react';
import { ButtonSolid } from '@/_components/AppButton';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
export const LinkExpiredInfoScreen = function LinkExpiredInfoScreen({ show = true }) {
const history = useHistory();
const navigate = useNavigate();
const darkMode = localStorage.getItem('darkMode') === 'true';
return (
@ -30,7 +30,7 @@ export const LinkExpiredInfoScreen = function LinkExpiredInfoScreen({ show = tru
<ButtonSolid
variant="secondary"
className="link-expired-info-btn"
onClick={() => history.push('/signup')}
onClick={() => navigate('/signup')}
data-cy="back-to-signup-button"
>
Back to signup

View file

@ -1,7 +1,9 @@
import React from 'react';
import { ButtonSolid } from '@/_components/AppButton';
import { useNavigate } from 'react-router-dom';
export const PasswordResetinfoScreen = function PasswordResetinfoScreen({ props, darkMode }) {
export const PasswordResetinfoScreen = function PasswordResetinfoScreen({ darkMode }) {
const navigate = useNavigate();
return (
<div className="info-screen-wrapper">
<div className="password-reset-card">
@ -24,7 +26,7 @@ export const PasswordResetinfoScreen = function PasswordResetinfoScreen({ props,
</p>
<ButtonSolid
variant="secondary"
onClick={() => props.history.push('/login')}
onClick={() => navigate('/login')}
className="reset-password-info-btn"
data-cy="back-to-login-button"
>

View file

@ -4,7 +4,7 @@ import GoogleSSOLoginButton from '@ee/components/LoginPage/GoogleSSOLoginButton'
import GitSSOLoginButton from '@ee/components/LoginPage/GitSSOLoginButton';
import OnBoardingForm from '../OnBoardingForm/OnBoardingForm';
import { authenticationService } from '@/_services';
import { useLocation, useHistory } from 'react-router-dom';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { LinkExpiredInfoScreen } from '@/SuccessInfoScreen';
import { ShowLoading } from '@/_components';
import { toast } from 'react-hot-toast';
@ -30,17 +30,18 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
const { t } = useTranslation();
const location = useLocation();
const history = useHistory();
const navigate = useNavigate();
const params = useParams();
const organizationId = new URLSearchParams(location?.state?.search).get('oid');
const organizationId = new URLSearchParams(location?.search).get('oid');
const single_organization = window.public_config?.DISABLE_MULTI_WORKSPACE === 'true';
const source = new URLSearchParams(location?.state?.search).get('source');
const source = new URLSearchParams(location?.search).get('source');
const darkMode = localStorage.getItem('darkMode') === 'true';
const getUserDetails = () => {
setIsLoading(true);
authenticationService
.verifyToken(location?.state?.token, location?.state?.organizationToken)
.verifyToken(params?.token, params?.organizationToken)
.then((data) => {
if (data?.redirect_url) {
window.location.href = buildURLWithQuery(data.redirect_url, {
@ -52,7 +53,7 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
setUserDetails(data);
setIsLoading(false);
if (data?.email !== '') {
if (location?.state?.organizationToken) {
if (params?.organizationToken) {
setShowJoinWorkspace(true);
return;
}
@ -122,8 +123,8 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
companyName: '',
companySize: '',
role: '',
token: location?.state?.token,
organizationToken: location?.state?.organizationToken ?? '',
token: params?.token,
organizationToken: params?.organizationToken ?? '',
source: source,
password: password,
})
@ -131,7 +132,7 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
authenticationService.updateUser(user);
authenticationService.deleteLoginOrganizationId();
setIsLoading(false);
history.push('/');
navigate('/');
})
.catch((res) => {
setIsLoading(false);
@ -381,8 +382,8 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
{verifiedToken && showOnboarding && (
<OnBoardingForm
userDetails={userDetails}
token={location?.state?.token}
organizationToken={location?.state?.organizationToken ?? ''}
token={params?.token}
organizationToken={params?.organizationToken ?? ''}
password={password}
darkMode={darkMode}
/>

View file

@ -15,7 +15,7 @@ const Filter = ({ filters, setFilters, handleBuildFilterQuery, resetFilterQuery
const popover = (
<Popover id="storage-filter-popover" className={cx({ 'theme-dark': darkMode })} data-cy="filter-section">
<Popover.Content bsPrefix="storage-filter-popover">
<Popover.Body bsPrefix="storage-filter-popover">
<div className="card-body" data-cy="filter-card-body">
{Object.values(filters).map((filter, index) => {
return (
@ -47,7 +47,7 @@ const Filter = ({ filters, setFilters, handleBuildFilterQuery, resetFilterQuery
</svg>
&nbsp;Add Condition
</div>
</Popover.Content>
</Popover.Body>
</Popover>
);

View file

@ -38,7 +38,7 @@ const Sort = ({ filters, setFilters, handleBuildSortQuery, resetSortQuery }) =>
const popover = (
<Popover id="storage-filter-popover" className={cx({ 'theme-dark': darkMode })} data-cy="sort-section">
<Popover.Content bsPrefix="storage-filter-popover">
<Popover.Body bsPrefix="storage-filter-popover">
<div className="card-body" data-cy="sort-card-body">
{Object.values(filters).map((filter, index) => {
return (
@ -68,7 +68,7 @@ const Sort = ({ filters, setFilters, handleBuildSortQuery, resetSortQuery }) =>
</svg>
&nbsp;Add another
</div>
</Popover.Content>
</Popover.Body>
</Popover>
);

View file

@ -9,7 +9,7 @@ export const TablePopover = ({ disabled, children, onEdit, onDelete }) => {
if (disabled) return children;
const popover = (
<Popover>
<Popover.Content>
<Popover.Body>
{/* <div className="w-min-100 row list-group-item-action cursor-pointer">
<div className="col-auto">
<EditIcon />
@ -26,7 +26,7 @@ export const TablePopover = ({ disabled, children, onEdit, onDelete }) => {
Delete
</div>
</div>
</Popover.Content>
</Popover.Body>
</Popover>
);

View file

@ -12,7 +12,7 @@ export const ListItemPopover = ({ onEdit, onDelete, darkMode }) => {
const popover = (
<Popover id="popover-contained" className="table-list-items">
<Popover.Content className={`${darkMode && 'theme-dark'}`}>
<Popover.Body className={`${darkMode && 'theme-dark'}`}>
<div className={`row cursor-pointer`}>
<div className="col-auto" data-cy="edit-option-icon">
<EditIcon />
@ -42,7 +42,7 @@ export const ListItemPopover = ({ onEdit, onDelete, darkMode }) => {
Delete
</div>
</div>
</Popover.Content>
</Popover.Body>
</Popover>
);
@ -62,8 +62,11 @@ export const ListItemPopover = ({ onEdit, onDelete, darkMode }) => {
trigger="click"
placement="bottom"
overlay={popover}
transition={false}
>
<EllipsisIcon />
<span>
<EllipsisIcon />
</span>
</OverlayTrigger>
</div>
);

View file

@ -1,7 +1,7 @@
import React, { useState, useEffect, useCallback } from 'react';
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { FilterPreview } from '@/_components';
import PropTypes from 'prop-types';
import Select, { fuzzySearch } from 'react-select-search';
import Select from 'react-select-search';
import '@/_styles/widgets/multi-select.scss';
function MultiSelect({
@ -10,18 +10,18 @@ function MultiSelect({
selectedValues = [],
onReset,
placeholder = 'Select',
options,
isLoading,
className,
searchLabel,
}) {
const [searchText, setSearchText] = useState('');
const [filteredOptions, setOptions] = useState([]);
const listOfOptions = useRef([]);
useEffect(() => {
options && setOptions(filterOptions(options));
setOptions(filterOptions(listOfOptions.current));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [options, selectedValues]);
}, [selectedValues, listOfOptions.current]);
const searchFunction = useCallback(
async (query) => {
@ -30,7 +30,8 @@ function MultiSelect({
return [];
}
const options = await onSearch(query);
return filterOptions(options);
listOfOptions.current = filterOptions(options);
return listOfOptions.current;
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[setSearchText, onSearch, selectedValues]
@ -49,7 +50,7 @@ function MultiSelect({
<Select
className={className}
getOptions={onSearch ? searchFunction : undefined}
options={onSearch ? [] : filteredOptions}
options={filteredOptions}
closeOnSelect={false}
search={true}
multiple
@ -59,7 +60,7 @@ function MultiSelect({
debounce={onSearch ? 300 : undefined}
printOptions="on-focus"
emptyMessage={
options?.length > 0
filteredOptions?.length > 0
? 'Not Found'
: searchText
? 'Not found'
@ -68,7 +69,7 @@ function MultiSelect({
: 'Please enter some text'
}
disabled={isLoading}
filterOptions={fuzzySearch}
fuzzySearch
/>
</div>
);

View file

@ -70,7 +70,6 @@ const Modal = ({ children, handleClose, portalStyles, styles, componentName, dar
type="button"
className="btn mx-2 btn-light"
onClick={handleClose}
data-tip="Hide code editor modal"
style={{ backgroundColor: darkMode && '#42546a' }}
>
<img

View file

@ -1,61 +1,46 @@
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { Navigate, useLocation } from 'react-router-dom';
import { authenticationService } from '@/_services';
export const PrivateRoute = ({ component: Component, switchDarkMode, darkMode, ...rest }) => (
<Route
{...rest}
render={(props) => {
const currentUser = authenticationService.currentUserValue;
if (!currentUser && !props.location.pathname.startsWith('/applications/')) {
// not logged in so redirect to login page with the return url
return (
<Redirect
to={{
pathname: '/login',
search: `?redirectTo=${props.location.pathname}`,
state: { from: props.location },
}}
/>
);
}
export const PrivateRoute = ({ children }) => {
const location = useLocation();
const currentUser = authenticationService.currentUserValue;
return !currentUser && !location.pathname.startsWith('/applications/') ? (
<Navigate
to={{
pathname: '/login',
search: `?redirectTo=${location.pathname}`,
state: { from: location },
}}
replace
/>
) : (
children
);
};
// authorised so return component
return <Component {...props} switchDarkMode={switchDarkMode} darkMode={darkMode} />;
}}
/>
);
export const AdminRoute = ({ component: Component, switchDarkMode, darkMode, ...rest }) => (
<Route
{...rest}
render={(props) => {
const currentUser = authenticationService.currentUserValue;
if (!currentUser && !props.location.pathname.startsWith('/applications/')) {
return (
<Redirect
to={{
pathname: '/login',
search: `?redirectTo=${props.location.pathname}`,
state: { from: props.location },
}}
/>
);
}
if (!currentUser?.admin) {
return (
<Redirect
to={{
pathname: '/',
search: `?redirectTo=${props.location.pathname}`,
state: { from: props.location },
}}
/>
);
}
return <Component {...props} switchDarkMode={switchDarkMode} darkMode={darkMode} />;
}}
/>
);
export const AdminRoute = ({ children }) => {
const location = useLocation();
const currentUser = authenticationService.currentUserValue;
return !currentUser && !location.pathname.startsWith('/applications/') ? (
<Navigate
to={{
pathname: '/login',
search: `?redirectTo=${location.pathname}`,
state: { from: location },
}}
replace
/>
) : !currentUser?.admin ? (
<Navigate
to={{
pathname: '/',
search: `?redirectTo=${location.pathname}`,
state: { from: location },
}}
replace
/>
) : (
children
);
};

View file

@ -1,7 +1,6 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
import { authenticationService } from '@/_services';
import { history } from '@/_helpers';
import Avatar from '@/_ui/Avatar';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import { useTranslation } from 'react-i18next';
@ -10,10 +9,11 @@ import { ToolTip } from '@/_components/ToolTip';
export const Profile = function Header({ switchDarkMode, darkMode }) {
const { first_name, last_name, avatar_id } = authenticationService.currentUserValue;
const { t } = useTranslation();
const navigate = useNavigate();
function logout() {
authenticationService.logout();
history.push('/login');
navigate('/login');
}
const getOverlay = () => {

View file

@ -339,7 +339,7 @@ function showModal(_ref, modal, show) {
function logoutAction(_ref) {
localStorage.clear();
_ref.props.history.push('/login');
_ref.props.navigate('/login');
window.location.href = '/login';
return Promise.resolve();
@ -407,8 +407,7 @@ export const executeAction = (_ref, event, mode, customVariables) => {
}
if (mode === 'view') {
_ref.props.history.push(url);
_ref.props.history.go();
_ref.props.navigate(url);
} else {
if (confirm('The app will be opened in a new tab as the action is triggered from the editor.')) {
window.open(urlJoin(window.public_config?.TOOLJET_HOST, url));
@ -870,6 +869,7 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode =
const options = getQueryVariables(dataQuery.options, _ref.state.currentState);
if (dataQuery.options.requestConfirmation) {
// eslint-disable-next-line no-unsafe-optional-chaining
const queryConfirmationList = _ref.state?.queryConfirmationList ? [..._ref.state?.queryConfirmationList] : [];
const queryConfirmation = {
queryId,

View file

@ -1,5 +0,0 @@
import { createBrowserHistory } from 'history';
export const history = createBrowserHistory({
basename: window.public_config?.SUB_PATH || '/',
});

View file

@ -1,4 +1,3 @@
export * from './auth-header';
export * from './handle-response';
export * from './history';
export * from './cookie';

View file

@ -0,0 +1,10 @@
import React from 'react';
import { useParams, useNavigate, useLocation } from 'react-router-dom';
export const withRouter = (WrappedComponent) => (props) => {
const params = useParams();
const location = useLocation();
const navigate = useNavigate();
return <WrappedComponent {...props} params={params} location={location} navigate={navigate} />;
};

View file

@ -1,19 +1,16 @@
import { useMemo } from 'react';
import { useParams, useLocation, useHistory, useRouteMatch } from 'react-router-dom';
import { useParams, useNavigate, useLocation } from 'react-router-dom';
import queryString from 'query-string';
export default function useRouter() {
const params = useParams();
const location = useLocation();
const history = useHistory();
const match = useRouteMatch();
const history = useNavigate();
// Return our custom router object
// Memoize so that a new object is only returned if something changes
return useMemo(() => {
return {
// For convenience add push(), replace(), pathname at top level
push: history.push,
replace: history.replace,
pathname: location.pathname,
// Merge params and parsed query string into single 'query' object
// so that they can be used interchangeably.
@ -24,9 +21,8 @@ export default function useRouter() {
},
// Include match, location, history objects so we have
// access to extra React Router functionality if needed.
match,
location,
history,
};
}, [params, match, location, history]);
}, [params, location, history]);
}

View file

@ -0,0 +1,180 @@
/**
* Main wrapper
*/
@import "../colors.scss";
.select-search-container {
--select-search-background: #fff;
--select-search-border: #dadcde;
--select-search-selected: #dadcde;
--select-search-text: 232e3c;
--select-search-subtle-text: #6c6f85;
--select-search-inverted-text: var(--select-search-background);
--select-search-highlight: #F8FAFF;
--select-search-font: "Roboto", sans-serif;
width: 100%;
position: relative;
font-family: var(--select-search-font);
color: var(--select-search-text);
box-sizing: border-box;
}
.main-wrapper .select-search-container {
--select-search-background: #fff;
--select-search-border: #dce0e8;
--select-search-selected: #1e66f5;
--select-search-text: #000;
--select-search-subtle-text: #6c6f85;
--select-search-highlight: #eff1f5;
}
.popover.popover-dark-themed .select-search-container,
.main-wrapper.theme-dark .select-search-container {
--select-search-background: #272822;
--select-search-border: #333c48;
--select-search-selected: #89b4fa;
--select-search-text: #fff;
--select-search-subtle-text: #a6adc8;
--select-search-highlight: #1e1e2e;
}
.select-search-container *,
.select-search-container *::after,
.select-search-container *::before {
box-sizing: inherit;
}
.select-search-input {
position: relative;
z-index: 1;
display: block;
width: 100%;
background: var(--select-search-background);
border: 1px solid var(--select-search-border);
color: var(--select-search-text);
border-radius: 4px !important;
outline: none;
padding: 0.4375rem 0.75rem;
font-size: 0.875rem;
font-weight: 400;
line-height: 1.4285714;
text-align: left;
text-overflow: ellipsis;
letter-spacing: 0.01rem;
-webkit-appearance: none;
-webkit-font-smoothing: antialiased;
}
.select-search-is-multiple .select-search-input {
margin-bottom: -2px;
}
.select-search-is-multiple .select-search-input {
border-radius: 3px 3px 0 0;
}
.select-search-input::-webkit-search-decoration,
.select-search-input::-webkit-search-cancel-button,
.select-search-input::-webkit-search-results-button,
.select-search-input::-webkit-search-results-decoration {
-webkit-appearance:none;
}
.select-search-input[readonly] {
cursor: pointer;
}
.select-search-is-disabled .select-search-input {
cursor: not-allowed;
}
.select-search-container:not(.select-search-is-disabled).select-search-has-focus .select-search-input,
.select-search-container:not(.select-search-is-disabled) .select-search-input:hover {
border-color: var(--select-search-selected);
}
.select-search-select {
background: var(--select-search-background);
border: 1px solid $color-dark-indigo-09;
background-color: #fff;
box-shadow: 0 0 0 1px #0000001a, 0 4px 11px #0000001a;
overflow: auto;
max-height: 360px;
}
.select-search-container:not(.select-search-is-multiple) .select-search-select {
position: absolute;
z-index: 2;
top: 44px;
right: 0;
left: 0;
border-radius: 4px;
display: none;
}
.select-search-container:not(.select-search-is-multiple).select-search-has-focus .select-search-select {
display: block;
}
.select-search-has-focus .select-search-select {
border-color: var(--select-search-selected);
}
.select-search-options {
list-style: none;
padding-left: 0px;
margin-bottom: 0px;
}
.select-search-option,
.select-search-not-found {
display: block;
padding: 8px 12px;
width: 100%;
background: var(--select-search-background);
border: none;
outline: none;
font-family: var(--select-search-font);
color: var(--select-search-text);
font-size: 0.875rem;
text-align: left;
letter-spacing: 0.01rem;
cursor: pointer;
-webkit-font-smoothing: antialiased;
}
.select-search-option:disabled {
opacity: 0.5;
cursor: not-allowed;
background: transparent !important;
}
.select-search-is-highlighted,
.select-search-option:not(.select-search-is-selected):hover {
background: var(--select-search-highlight);
}
.select-search-is-selected {
font-weight: bold;
color: var(--select-search-selected);
}
.select-search-group-header {
font-size: 12px;
text-transform: uppercase;
background: var(--select-search-border);
color: var(--select-search-subtle-text);
letter-spacing: 0.1rem;
padding: 0px;
}
.select-search-row:not(:first-child) .select-search-group-header {
margin-top: 5px;
}
.select-search-row:not(:last-child) .select-search-group-header {
margin-bottom: 5px;
}
.select-search-row button{
border-radius: 0px;
}

View file

@ -6412,15 +6412,11 @@ tbody {
}
.dropdown-table-column-hide-common {
position: absolute;
z-index: 10;
right: 0;
border-radius: 3px;
height: auto;
overflow-y: scroll;
padding: 8px 16px;
width: 20rem;
top: 20px;
max-height: 200px;
}
@ -7457,6 +7453,9 @@ tbody {
}
}
.react-tooltip {
font-size: .765625rem !important;
}
.tj-db-table {
overflow-y: auto;
height: 110px;

View file

@ -6,6 +6,15 @@
border: 1px solid #dadcde;
border-radius: 4px;
.select-search__select, .select-search-dark__select{
display: none;
}
.has-focus>.select-search__select,
.has-focus>.select-search-dark__select {
display: block;
}
.select-search-dark.is-loading .select-search-dark__value::after {
background-size: 10px;
}

View file

@ -1,9 +1,11 @@
import React from 'react';
import { userService } from '@/_services';
import cx from 'classnames';
import { Tooltip } from 'react-tooltip';
// eslint-disable-next-line no-unused-vars
const Avatar = ({ text, image, avatarId, title = '', borderColor = '', borderShape }) => {
const Avatar = ({ text, image, avatarId, title = '', borderColor = '', borderShape, indexId = 0 }) => {
const formattedTitle = String(title).toLowerCase().replace(/\s+/g, '-');
const [avatar, setAvatar] = React.useState();
React.useEffect(() => {
@ -19,7 +21,8 @@ const Avatar = ({ text, image, avatarId, title = '', borderColor = '', borderSha
return (
<span
data-tip={title}
data-tooltip-id={`tooltip-for-avatar-${formattedTitle}-${indexId}`}
data-tooltip-content={title}
style={{
// border: borderColor ? `1.5px solid ${borderColor}` : 'none',
...(image || avatar ? { backgroundImage: `url(${avatar ?? image})` } : {}),
@ -31,6 +34,7 @@ const Avatar = ({ text, image, avatarId, title = '', borderColor = '', borderSha
data-cy="avatar-image"
>
{!image && !avatarId && text}
<Tooltip id={`tooltip-for-avatar-${formattedTitle}-${indexId}`} className="tooltip" />
</span>
);
};

View file

@ -1,10 +1,9 @@
import React from 'react';
import { Link } from 'react-router-dom';
// todo: legacy package, remove this and upgrade to react-router-dom v6 (https://reactrouter.com/en/main/upgrading/v5)
// v6 has an official way to support breadcrumbs https://reactrouter.com/en/main/hooks/use-matches#breadcrumbs
import withBreadcrumbs from 'react-router-breadcrumbs-hoc';
import useBreadcrumbs from 'use-react-router-breadcrumbs';
const Breadcrumbs = ({ breadcrumbs }) => {
export const Breadcrumbs = () => {
const breadcrumbs = useBreadcrumbs(routes, { excludePaths: ['/'] });
return (
<ol className="breadcrumb breadcrumb-arrows">
{breadcrumbs.length === 0 && (
@ -29,5 +28,3 @@ const routes = [
{ path: '/database', breadcrumb: 'Tables', dataCy: 'tables-page-header' },
{ path: '/workspace-settings', breadcrumb: 'Workspace settings' },
];
export default withBreadcrumbs(routes, { excludePaths: ['/'] })(Breadcrumbs);

View file

@ -1,6 +1,6 @@
import React from 'react';
import cx from 'classnames';
import Breadcrumbs from '../Breadcrumbs';
import { Breadcrumbs } from '../Breadcrumbs';
import { OrganizationList } from '@/_components/OrganizationManager/List';
function Header() {

View file

@ -20,7 +20,7 @@ const PopoverComponent = ({
const computeStyle = () => {
if (popoverContentHeight) {
return {
height: `${popoverContentHeight}vh`,
height: popoverContentHeight === 'auto' ? 'auto' : `${popoverContentHeight}vh`,
overflow: 'auto',
};
}

View file

@ -23,8 +23,10 @@ export const SearchBox = ({ onChange, ...restProps }) => {
}
return () => {
document.querySelector('.searchbox-wrapper .input-icon .form-control:not(:first-child)').style.paddingLeft =
'2.5rem';
if (document.querySelector('.searchbox-wrapper .input-icon .form-control:not(:first-child)')) {
document.querySelector('.searchbox-wrapper .input-icon .form-control:not(:first-child)').style.paddingLeft =
'2.5rem';
}
};
}, [searchText]);

View file

@ -29,6 +29,7 @@ export default function styles(darkMode, width = 224, height = 32, styles = {})
}),
valueContainer: (provided, state) => ({
...provided,
display: 'flex',
height: height,
marginBottom: '4px',
}),

View file

@ -1,8 +1,8 @@
import React from 'react';
import { render } from 'react-dom';
import { createRoot } from 'react-dom/client';
import * as Sentry from '@sentry/react';
import { Integrations } from '@sentry/tracing';
import { createBrowserHistory } from 'history';
import { useLocation, useNavigationType, createRoutesFromChildren, matchRoutes } from 'react-router-dom';
import { BrowserTracing } from '@sentry/tracing';
import { appService } from '@/_services';
import { App } from './App';
// eslint-disable-next-line import/no-unresolved
@ -33,7 +33,6 @@ appService
});
if (window.public_config.APM_VENDOR === 'sentry') {
const history = createBrowserHistory();
const tooljetServerUrl = window.public_config.TOOLJET_SERVER_URL;
const tracingOrigins = ['localhost', /^\//];
const releaseVersion = window.public_config.RELEASE_VERSION
@ -47,8 +46,14 @@ appService
debug: !!window.public_config.SENTRY_DEBUG,
release: releaseVersion,
integrations: [
new Integrations.BrowserTracing({
routingInstrumentation: Sentry.reactRouterV5Instrumentation(history),
new BrowserTracing({
routingInstrumentation: Sentry.reactRouterV6Instrumentation(
React.useEffect,
useLocation,
useNavigationType,
createRoutesFromChildren,
matchRoutes
),
tracingOrigins: tracingOrigins,
}),
],
@ -56,4 +61,4 @@ appService
});
}
})
.then(() => render(<AppWithProfiler />, document.getElementById('app')));
.then(() => createRoot(document.getElementById('app')).render(<AppWithProfiler />));

View file

@ -3,6 +3,7 @@ const webpack = require('webpack');
const path = require('path');
const CompressionPlugin = require('compression-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const hash = require('string-hash');
const environment = process.env.NODE_ENV === 'production' ? 'production' : 'development';
@ -72,14 +73,21 @@ module.exports = {
},
{
test: /\.svg$/,
use: [
{
loader: '@svgr/webpack',
options: {
limit: 10000,
use: ({ resource }) => ({
loader: '@svgr/webpack',
options: {
svgoConfig: {
plugins: [
{
name: 'prefixIds',
cleanupIDs: {
prefix: `svg-${hash(resource)}`,
},
},
],
},
},
],
}),
},
{
test: /\.css$/,

2477
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -19,9 +19,9 @@
},
"devDependencies": {
"@tooljet/cli": "^0.0.13",
"eslint": "^7.25.0",
"husky": "^7.0.2",
"lint-staged": "^11.2.3"
"eslint": "^8.34.0",
"husky": "^8.0.3",
"lint-staged": "^13.1.2"
},
"scripts": {
"prebuild:plugins": "npm run install:plugins",

View file

@ -64,6 +64,7 @@ export class AppsService {
});
if (appVersion?.dataQueries) {
// eslint-disable-next-line no-unsafe-optional-chaining
for (const query of appVersion?.dataQueries) {
if (query?.plugin) {
query.plugin.manifestFile.data = JSON.parse(decode(query.plugin.manifestFile.data.toString('utf8')));