diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9eee484346..aa39961383 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,15 +36,15 @@ jobs: steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js 18.3.0 - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: 18.3.0 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@v3 env: cache-name: cache-node-modules with: @@ -66,15 +66,15 @@ jobs: github.ref == 'refs/heads/develop' steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js 18.3.0 - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: 18.3.0 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@v3 env: cache-name: cache-node-modules with: @@ -106,9 +106,9 @@ jobs: --health-timeout 5s --health-retries 5 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@v3 env: cache-name: cache-node-modules with: @@ -144,9 +144,9 @@ jobs: --health-timeout 5s --health-retries 5 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@v3 env: cache-name: cache-node-modules with: diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index ca0bdb0490..73c18b1d48 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -14,10 +14,10 @@ jobs: Run-Cypress: if: ${{ github.event.action == 'labeled' && github.event.label.name == 'run-cypress' }} - runs-on: ubuntu-latest + runs-on: self-hosted steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.ref }} @@ -33,6 +33,12 @@ jobs: sleep 1 done + - name: Getting cypress secrets + # use quotes around the secret, as its value + # is simply inserted as a string into the command + run: | + echo '${{ secrets.CYPRESS_SECRETS }}' > cypress.env.json + - name: Cypress integration test uses: cypress-io/github-action@v5 with: @@ -41,7 +47,7 @@ jobs: config-file: cypress-run.config.js - name: Capturing screenshots - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: always() with: name: screenshots diff --git a/.github/workflows/packer-build.yml b/.github/workflows/packer-build.yml index 5504214a02..8996aec316 100644 --- a/.github/workflows/packer-build.yml +++ b/.github/workflows/packer-build.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setting tag if: "${{ github.event.inputs.version != '' }}" @@ -28,7 +28,7 @@ jobs: run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/.version b/.version index 197c4d5c2d..3f5987a5cb 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.4.0 +2.4.9 diff --git a/cypress-tests/cypress-run.config.js b/cypress-tests/cypress-run.config.js index 66996c0ed7..4b4b658cc7 100644 --- a/cypress-tests/cypress-run.config.js +++ b/cypress-tests/cypress-run.config.js @@ -76,8 +76,10 @@ module.exports = defineConfig({ experimentalRunAllSpecs: true, baseUrl: "http://localhost:8082", specPattern: [ - "cypress/e2e/editor/widget/*.cy.js", - "cypress/e2e/editor/multipage/*.cy.js"], + "cypress/e2e/editor/app-version/version.cy.js", + "cypress/e2e/exportImport/export.cy.js", + "cypress/e2e/exportImport/import.cy.js", + "cypress/e2e/database/database.cy.js"], numTestsKeptInMemory: 1, redirectionLimit: 10, experimentalRunAllSpecs: true, diff --git a/cypress-tests/cypress.config.js b/cypress-tests/cypress.config.js index 67e170a670..5e74123f5f 100644 --- a/cypress-tests/cypress.config.js +++ b/cypress-tests/cypress.config.js @@ -13,7 +13,7 @@ module.exports = defineConfig({ requestTimeout: 10000, pageLoadTimeout: 20000, responseTimeout: 10000, - viewportWidth: 1200, + viewportWidth: 1440, viewportHeight: 960, chromeWebSecurity: false, trashAssetsBeforeRuns: true, @@ -50,13 +50,17 @@ module.exports = defineConfig({ on("task", { deleteFolder(folderName) { return new Promise((resolve, reject) => { - rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => { - if (err) { - console.error(err); - return reject(err); - } - resolve(null); - }); + if (fs.existsSync(folderName)) { + rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => { + if (err) { + console.error(err); + return reject(err); + } + return resolve(null); + }); + } else { + return resolve(null); + } }); }, }); @@ -73,8 +77,11 @@ module.exports = defineConfig({ experimentalModfyObstructiveThirdPartyCode: true, experimentalRunAllSpecs: true, baseUrl: "http://localhost:8082", - specPattern: "cypress/e2e/**/*.cy.js", - downloadsFolder:"cypress/downloads", + specPattern: [ + "cypress/e2e/editor/widget/*.cy.js", + "cypress/e2e/multipage/*.cy.js", + ], + downloadsFolder: "cypress/downloads", numTestsKeptInMemory: 25, redirectionLimit: 10, experimentalRunAllSpecs: true, diff --git a/cypress-tests/cypress/constants/selectors/common.js b/cypress-tests/cypress/constants/selectors/common.js index dd1689be43..63e0a36d7c 100644 --- a/cypress-tests/cypress/constants/selectors/common.js +++ b/cypress-tests/cypress/constants/selectors/common.js @@ -3,8 +3,9 @@ export const cyParamName = (paramName = "") => { }; export const commonSelectors = { - toastMessage: ".go3958317564", + toastMessage: ".go3958317564", oldToastMessage: ".go318386747", + newToastMessage:'.drawer-container > [style="position: fixed; z-index: 9999; inset: 16px; pointer-events: none;"] > .go4109123758 > .go2072408551', toastCloseButton: '[data-cy="toast-close-button"]', editButton: "[data-cy=edit-button]", searchField: "[data-cy=widget-search-box]", @@ -42,7 +43,7 @@ export const commonSelectors = { createNewFolderButton: "[data-cy=create-new-folder-button]", folderNameInput: "[data-cy=folder-name-input]", createFolderButton: "[data-cy=create-folder-button]", - folderList: ".css-2kg7t4-MenuList", + folderList: ".css-169zxdi-MenuList", empytyFolderImage: "[data-cy=empty-folder-image]", emptyFolderText: "[data-cy=empty-folder-text]", allApplicationsLink: "[data-cy=all-applications-link]", @@ -56,9 +57,7 @@ export const commonSelectors = { viewerPageLogo: '[data-cy="viewer-page-logo"]', lastPageArrow: '[data-cy="last-page-link"]', nextPageArrow: '[data-cy="next-page-link"]', - emailFilterInput: '[data-cy="email-filter-input-field"]', - firstNameFilterInput: '[data-cy="first-name-filter-input-field"]', - lastNameFilterInput: '[data-cy="last-name-filter-input-field"]', + inputUserSearch: '[data-cy="input-field-user-filter-search"]', filterButton: '[data-cy="filter-button"]', copyIcon: '[data-cy="copy-icon"]', addWorkspaceButton: '[data-cy="add-new-workspace-link"]', @@ -84,11 +83,11 @@ export const commonSelectors = { acceptInviteButton: '[data-cy="accept-invite-button"]', databaseIcon: `[data-cy="database-icon"]`, profileSettings: '[data-cy="profile-settings"]', - workspaceSettingsIcon: '[data-cy="workspace-settings-icon"]', - manageUsersOption: '[data-cy="manage-users-option"]', - manageGroupsOption: '[data-cy="manage-groups-option"]', - manageSSOOption: '[data-cy="manage-sso-option"]', - workspaceVariableOption: '[data-cy="workspace-variable-option"]', + workspaceSettingsIcon: '[data-cy="icon-workspace-settings"]', + manageUsersOption: '[data-cy="users-list-item"]', + manageGroupsOption: '[data-cy="groups-list-item"]', + manageSSOOption: '[data-cy="sso-list-item"]', + workspaceVariableOption: '[data-cy="workspace-variables-list-item"]', clearFilterButton: '[data-cy="clear-filter-button"]', userStatusSelect: '[data-cy="user-status-select-continer"]', emailInputLabel: '[data-cy="email-input-label"]', @@ -159,6 +158,15 @@ export const commonSelectors = { resetPasswordButton: '[data-cy="reset-password-button"]', resetPasswordPageDescription: '[data-cy="reset-password-page-description"]', backToLoginButton: '[data-cy="back-to-login-button"]', + breadcrumbTitle:'[data-cy="breadcrumb-title"]', + breadcrumbPageTitle:'[data-cy="breadcrumb-page-title"]', + labelFullNameInput: '[data-cy="label-full-name-input-field"]', + inputFieldFullName: '[data-cy="input-field-full-name"]', + labelEmailInput: '[data-cy="label-email-input-field"]', + inputFieldEmailAddress: '[data-cy="input-field-email"]', + closeButton: '[data-cy="close-button"]', + + onboardingRadioButton: (radioButtonText) => { return `[data-cy="${cyParamName(radioButtonText)}-radio-button"]`; diff --git a/cypress-tests/cypress/constants/selectors/dashboard.js b/cypress-tests/cypress/constants/selectors/dashboard.js index 844e8374ea..dac863ed55 100644 --- a/cypress-tests/cypress/constants/selectors/dashboard.js +++ b/cypress-tests/cypress/constants/selectors/dashboard.js @@ -22,7 +22,7 @@ export const dashboardSelector = { changeButton: "[data-cy=change-button]", addToFolderTitle: "[data-cy=add-to-folder-title]", moveAppText: "[data-cy=move-selected-app-to-text]", - selectFolder: '[data-cy="select-folder"]>>>.css-s59k37-ValueContainer', + selectFolder: '[data-cy="select-folder"]>>>>.css-h380uj-Input', addToFolderButton: "[data-cy=add-to-folder-button]", appTemplateRow: '[data-cy="app-template-row"]', homePageContent: '[data-cy="home-page-content"]', diff --git a/cypress-tests/cypress/constants/selectors/exportImport.js b/cypress-tests/cypress/constants/selectors/exportImport.js index 0313e34d5b..5de0135a1c 100644 --- a/cypress-tests/cypress/constants/selectors/exportImport.js +++ b/cypress-tests/cypress/constants/selectors/exportImport.js @@ -10,12 +10,12 @@ export const appVersionSelectors = { createNewVersion: '[data-cy="create-new-version-title"]', versionNamelabel: '[data-cy="version-name-label"]', appVersionMenuField: - '[data-cy="app-version-selector"] .custom-version-selector__indicators', + '[data-cy="app-version-selector"] .undefined__indicators', versionNameInputField: '[data-cy="version-name-input-field"]', createVersionFromLabel: '[data-cy="create-version-from-label"]', createVersionInputField: '[data-cy="create-version-from-input-field"]', createNewVersionButton: '[data-cy="create-new-version-button"]', - appVersionContentList: ".custom-version-selector__menu-list", + appVersionContentList: ".undefined__menu-list", }; export const exportAppModalSelectors = { selectVersionTitle: '[data-cy= "select-a-version-to-export-title"]', diff --git a/cypress-tests/cypress/constants/selectors/manageUsers.js b/cypress-tests/cypress/constants/selectors/manageUsers.js index 277bed2159..de980f279d 100644 --- a/cypress-tests/cypress/constants/selectors/manageUsers.js +++ b/cypress-tests/cypress/constants/selectors/manageUsers.js @@ -1,27 +1,31 @@ export const usersSelector = { dropdown: "[data-cy=workspace-dropdown]", - inviteUserButton: "[data-cy=invite-new-user]", + buttonAddUsers: "[data-cy=button-invite-new-user]", usersElements: { - pageTitle: "[data-cy=users-page-title]", - nameTitile: "[data-cy=name-title]", - emailTitle: "[data-cy=email-title]", - statusTitle: "[data-cy=status-title]", + usersTableNameColumnHeader: '[data-cy="users-table-name-column-header"]', + usersTableEmailColumnHeader: '[data-cy="users-table-email-column-header"]', + usersTableStatusColumnHeader: '[data-cy="users-table-status-column-header"]', + usersFilterLabel: '[data-cy="users-filter-label"]' }, + usersPageTitle: '[data-cy="title-users-page"]', + userFilterInput: '[data-cy="users-filter-input"]', adminUserName: "[data-cy=user-name]", adminUserEmail: "[data-cy=user-email]", userStatus: "[data-cy=user-status]:eq(0)", userState: "[data-cy=user-state]:eq(0)", userStatus: "[data-cy=user-status]", - cardTitle: "[data-cy=add-new-user]", - firstNameInput: "[data-cy=first-name-input]", + addUsersCardTitle: '[data-cy="add-users-card-title"]', + + inputFieldName: "[data-cy=first-name-input]", lastNameInput: "[data-cy=last-name-input]", emailLabel: "[data-cy=email-label]", emailInput: "[data-cy=email-input]", cancelButton: "[data-cy=cancel-button]", - createUserButton: "[data-cy=create-user-button]", - fisrtNameError: "[data-cy=first-name-error]", - lastNameError: "[data-cy=last-name-error]", - emailError: "[data-cy=email-error]", + buttonInviteUsers: '[data-cy="button-invite-users"]', + buttonInviteWithEmail: '[data-cy="button-invite-with-email"]', + buttonUploadCsvFile: '[data-cy="button-upload-csv-file"]', + fullNameError: '[data-cy="error-message-fullname"]', + emailError: '[data-cy="error-message-email"]', pageLogo: "[data-cy=page-logo]", invitePageHeader: '[data-cy="invite-page-header"]', invitePgaeSubHeader: '[data-cy="invite-page-sub-header"]', @@ -47,7 +51,12 @@ export const usersSelector = { inviteBulkUserButton: '[data-cy="invite-bulk-user-button"]', bulkUserUploadPageTitle: '[data-cy="bulk-user-upload-page-title"]', bulkUSerUploadInput: '[data-cy="bulk-user-upload-input"]', - downloadTemplateButton: '[data-cy="download-template-button"]', - createUsersButton: '[data-cy="create-users-button"]', - + buttonDownloadTemplate: '[data-cy="button-download-template"]', + buttonUploadUsers: '[data-cy="button-upload-users"]', + helperTextBulkUpload: '[data-cy="helper-text-bulk-upload"]', + iconBulkUpload:'[data-cy="icon-bulk-upload"]', + helperTextSelectFile:'[data-cy="helper-text-select-file"]', + helperTextDropFile: '[data-cy="helper-text-drop-file"]', + inputFieldBulkUpload: '[data-cy="input-field-bulk-upload"]', + copyInvitationLink: '[data-cy="copy-invitation-link"]', }; diff --git a/cypress-tests/cypress/constants/texts/common.js b/cypress-tests/cypress/constants/texts/common.js index c0e0c08d8d..aa05dfde22 100644 --- a/cypress-tests/cypress/constants/texts/common.js +++ b/cypress-tests/cypress/constants/texts/common.js @@ -123,6 +123,14 @@ export const commonText = { backToLoginButton: "Back to log in", resetPasswordPageDescription: "Your password has been reset successfully, log into ToolJet to continue your session", + labelFullNameInput: "Enter full name", + labelEmailInput: "Email address", + breadcrumbworkspaceSettingTitle:"Workspace settings", + breadcrumbGlobalDatasourceTitle: "Global datasources", + breadcrumbDatabaseTitle: "Databse", + breadcrumbApplications: "Applications", + breadcrumbSettings: "Settings", + emailPageDescription: (email) => { return `We’ve sent an email to ${email} with a verification link. Please use that to verify your email address.`; }, diff --git a/cypress-tests/cypress/constants/texts/manageUsers.js b/cypress-tests/cypress/constants/texts/manageUsers.js index 979bb9ff6a..390a2d3750 100644 --- a/cypress-tests/cypress/constants/texts/manageUsers.js +++ b/cypress-tests/cypress/constants/texts/manageUsers.js @@ -1,20 +1,21 @@ export const usersText = { usersElements: { - pageTitle: "Users & Permissions", - nameTitile: "NAME", - emailTitle: "EMAIL", - statusTitle: "STATUS", - + usersTableNameColumnHeader: "NAME", + usersTableEmailColumnHeader: "EMAIL", + usersTableStatusColumnHeader: "STATUS", + usersFilterLabel: "Showing" }, + usersPageTitle: "users", + breadcrumbUsersPageTitle: " Users & permissions", adminUserName: "The Developer", adminUserEmail: "dev@tooljet.io", adminUserState: "Archive", - inviteUserButton: "Invite new user", - cardTitle: "Add new user", + buttonAddUsers: "Add users", + addUsersCardTitle: "Add users", emailLabel: "Email address", cancelButton: "Cancel", - createUserButton: "Create User", - fieldRequired: "This field is required", + buttonInviteUsers: "Invite users", + errorTextFieldRequired: "This field is required", exsitingEmail: "User with such email already exists.", userCreatedToast: "User has been created", inviteCopiedToast: "Invitation URL copied", @@ -47,6 +48,13 @@ export const usersText = { "Added to the workspace and password has been set successfully.", inviteBulkUserButton:"Invite bulk users", bulkUserUploadPageTitle: "Upload Users", - downloadTemplateButton: 'Download Template', - createUsersButton:"Create Users", + buttonDownloadTemplate: 'Download Template', + buttonUploadUsers:"Upload users", + + buttonInviteWithEmail: " Invite with email", + buttonUploadCsvFile: "Upload CSV file", + + helperTextBulkUpload: 'Download the ToolJet template to add user details or format your file in the same as the template. ToolJet won’t be able to recognise files in any other format. ', + helperTextSelectFile:'Select a CSV file to upload', + helperTextDropFile: 'Or drag and drop it here', }; diff --git a/cypress-tests/cypress/e2e/authentication/signUp.cy.js b/cypress-tests/cypress/e2e/authentication/signUp.cy.js index e02f59d8c3..24a5f24ef2 100644 --- a/cypress-tests/cypress/e2e/authentication/signUp.cy.js +++ b/cypress-tests/cypress/e2e/authentication/signUp.cy.js @@ -22,7 +22,9 @@ describe("User signup", () => { cy.visit("/"); }); it("Verify sign up page elements", () => { - cy.get(commonSelectors.createAnAccountLink).click(); + cy.wait(500); + cy.reload(); + cy.get(commonSelectors.createAnAccountLink).realClick(); SignUpPageElements(); cy.clearAndType(commonSelectors.nameInputField, data.fullName); diff --git a/cypress-tests/cypress/e2e/dashboard/dashboard.cy.js b/cypress-tests/cypress/e2e/dashboard/dashboard.cy.js index df16d00ced..1f90ca0075 100644 --- a/cypress-tests/cypress/e2e/dashboard/dashboard.cy.js +++ b/cypress-tests/cypress/e2e/dashboard/dashboard.cy.js @@ -27,7 +27,7 @@ describe("dashboard", () => { const data = {}; data.appName = `${fake.companyName}-App`; data.folderName = `${fake.companyName}-Folder`; - data.cloneAppName = `${data.appName}-Clone`; + data.cloneAppName = `cloned-${data.appName}`; data.updatedFolderName = `New-${data.folderName}`; beforeEach(() => { @@ -40,8 +40,11 @@ describe("dashboard", () => { cy.intercept("GET", "/api/apps?page=1&folder=&searchKey=", { fixture: "intercept/emptyDashboard.json", }).as("emptyDashboard"); + cy.intercept("GET", "/api/folders?searchKey=",{"folders":[]}).as("folders"); login(); cy.wait("@emptyDashboard"); + cy.wait("@folders"); + deleteDownloadsFolder(); }); @@ -51,6 +54,7 @@ describe("dashboard", () => { cy.get( commonSelectors.workspaceName ).verifyVisibleElement("have.text", "My workspace"); + cy.get(commonSelectors.workspaceName).click(); cy.get(commonSelectors.workspaceEditButton).should("be.visible"); cy.get(commonSelectors.appCreateButton).verifyVisibleElement( "have.text", @@ -304,6 +308,7 @@ describe("dashboard", () => { }); it("Should verify the app CRUD operation", () => { + data.appName = `${fake.companyName}-App`; cy.appUILogin(); cy.createApp(); cy.renameApp(data.appName); @@ -328,6 +333,7 @@ describe("dashboard", () => { }); it("Should verify the folder CRUD operation", () => { + data.appName = `${fake.companyName}-App`; cy.appUILogin(); cy.createApp(); cy.renameApp(data.appName); diff --git a/cypress-tests/cypress/e2e/dashboard/multi-workspace/manageUsers.cy.js b/cypress-tests/cypress/e2e/dashboard/multi-workspace/manageUsers.cy.js index f1c90a6763..f854d59dd4 100644 --- a/cypress-tests/cypress/e2e/dashboard/multi-workspace/manageUsers.cy.js +++ b/cypress-tests/cypress/e2e/dashboard/multi-workspace/manageUsers.cy.js @@ -1,11 +1,11 @@ -import { commonSelectors } from "Selectors/common"; +import { commonSelectors } from "Selectors/common" import { fake } from "Fixtures/fake"; +import { usersText } from "Texts/manageUsers" import { usersSelector } from "Selectors/manageUsers"; -import { usersText } from "Texts/manageUsers"; import * as users from "Support/utils/manageUsers"; import * as common from "Support/utils/common"; import { path } from "Texts/common"; -import { dashboardSelector } from "../../../constants/selectors/dashboard"; +import { dashboardSelector } from "Selectors/dashboard"; const data = {}; data.firstName = fake.firstName; @@ -21,106 +21,43 @@ describe("Manage Users for multiple workspace", () => { common.navigateToManageUsers(); users.manageUsersElements(); - cy.get(usersSelector.cancelButton).click(); - cy.get(usersSelector.usersElements.nameTitile).should("be.visible"); - cy.get(usersSelector.inviteUserButton).click(); + cy.get(commonSelectors.cancelButton).click(); + cy.get(usersSelector.usersPageTitle).should("be.visible"); + cy.get(usersSelector.buttonAddUsers).click(); - cy.get(usersSelector.createUserButton).click(); - cy.get(usersSelector.fisrtNameError).verifyVisibleElement( + cy.get(usersSelector.buttonInviteUsers).click(); + cy.get(usersSelector.fullNameError).verifyVisibleElement("have.text",usersText.errorTextFieldRequired); + cy.get(usersSelector.emailError).verifyVisibleElement("have.text",usersText.errorTextFieldRequired); + + cy.clearAndType(commonSelectors.inputFieldFullName, data.firstName); + cy.get(commonSelectors.inputFieldEmailAddress).clear(); + cy.get(usersSelector.buttonInviteUsers).click(); + cy.get(usersSelector.emailError).verifyVisibleElement("have.text",usersText.errorTextFieldRequired); + + cy.get(commonSelectors.inputFieldFullName).clear(); + cy.clearAndType(commonSelectors.inputFieldEmailAddress, data.email); + cy.get(usersSelector.buttonInviteUsers).click(); + cy.get(usersSelector.fullNameError).verifyVisibleElement( "have.text", - usersText.fieldRequired - ); - cy.get(usersSelector.lastNameError).verifyVisibleElement( - "have.text", - usersText.fieldRequired - ); - cy.get(usersSelector.emailError).verifyVisibleElement( - "have.text", - usersText.fieldRequired + usersText.errorTextFieldRequired ); - cy.clearAndType(usersSelector.firstNameInput, data.firstName); - cy.get(usersSelector.lastNameInput).clear(); - cy.get(usersSelector.emailInput).clear(); - cy.get(usersSelector.createUserButton).click(); - cy.get(usersSelector.lastNameError).verifyVisibleElement( - "have.text", - usersText.fieldRequired - ); - cy.get(usersSelector.emailError).verifyVisibleElement( - "have.text", - usersText.fieldRequired - ); - - cy.get(usersSelector.firstNameInput).clear(); - cy.get(usersSelector.emailInput).clear(); - cy.clearAndType(usersSelector.lastNameInput, data.lastName); - cy.get(usersSelector.createUserButton).click(); - cy.get(usersSelector.fisrtNameError).verifyVisibleElement( - "have.text", - usersText.fieldRequired - ); - cy.get(usersSelector.emailError).verifyVisibleElement( - "have.text", - usersText.fieldRequired - ); - - cy.get(usersSelector.firstNameInput).clear(); - cy.get(usersSelector.lastNameInput).clear(); - cy.clearAndType(usersSelector.emailInput, data.email); - cy.get(usersSelector.createUserButton).click(); - cy.get(usersSelector.fisrtNameError).verifyVisibleElement( - "have.text", - usersText.fieldRequired - ); - cy.get(usersSelector.lastNameError).verifyVisibleElement( - "have.text", - usersText.fieldRequired - ); - - cy.get(usersSelector.firstNameInput).clear(); - cy.clearAndType(usersSelector.lastNameInput, data.lastName); - cy.clearAndType(usersSelector.emailInput, data.email); - cy.get(usersSelector.createUserButton).click(); - cy.get(usersSelector.fisrtNameError).verifyVisibleElement( - "have.text", - usersText.fieldRequired - ); - - cy.get(usersSelector.lastNameInput).clear(); - cy.clearAndType(usersSelector.firstNameInput, data.firstName); - cy.clearAndType(usersSelector.emailInput, data.email); - cy.get(usersSelector.createUserButton).click(); - cy.get(usersSelector.lastNameError).verifyVisibleElement( - "have.text", - usersText.fieldRequired - ); - - cy.get(usersSelector.emailInput).clear(); - cy.clearAndType(usersSelector.firstNameInput, data.firstName); - cy.clearAndType(usersSelector.lastNameInput, data.lastName); - cy.get(usersSelector.createUserButton).click(); - cy.get(usersSelector.emailError).verifyVisibleElement( - "have.text", - usersText.fieldRequired - ); - - cy.clearAndType(usersSelector.firstNameInput, data.firstName); - cy.clearAndType(usersSelector.lastNameInput, data.lastName); + cy.clearAndType(commonSelectors.inputFieldFullName, data.firstName); cy.clearAndType( - usersSelector.emailInput, + commonSelectors.inputFieldEmailAddress, usersText.adminUserEmail ); - cy.get(usersSelector.createUserButton).click(); + cy.get(usersSelector.buttonInviteUsers).click(); + cy.verifyToastMessage( - commonSelectors.toastMessage, + commonSelectors.newToastMessage, usersText.exsitingEmail ); }); it("Should verify the confirm invite page and new user account", () => { common.navigateToManageUsers(); - users.inviteUser(data.firstName, data.lastName, data.email); + users.inviteUser(data.firstName, data.email); users.confirmInviteElements(); cy.clearAndType(commonSelectors.passwordInputField, "pass"); @@ -200,7 +137,7 @@ describe("Manage Users for multiple workspace", () => { cy.contains("td", data.email) .parent() .within(() => { - cy.get("td img").click(); + cy.get(usersSelector.copyInvitationLink).click(); }); cy.verifyToastMessage( commonSelectors.toastMessage, @@ -219,7 +156,6 @@ describe("Manage Users for multiple workspace", () => { cy.get("@copyToClipboardPrompt").then((prompt) => { common.logout(); cy.visit(prompt.args[0][1]); - cy.url().should("include", path.confirmInvite); }); cy.get(usersSelector.acceptInvite).click(); diff --git a/cypress-tests/cypress/e2e/editor/app-version/version.cy.js b/cypress-tests/cypress/e2e/editor/app-version/version.cy.js index d002a86f90..0d48eda0ac 100644 --- a/cypress-tests/cypress/e2e/editor/app-version/version.cy.js +++ b/cypress-tests/cypress/e2e/editor/app-version/version.cy.js @@ -11,7 +11,7 @@ import { buttonText } from "Texts/button"; import { verifyComponent, deleteComponentAndVerify } from "Support/utils/basicComponents"; -describe("App Export Functionality", () => { +describe("App Version Functionality", () => { var data = {}; data.appName = `${fake.companyName}-App`; let currentVersion = ""; @@ -73,12 +73,10 @@ describe("App Export Functionality", () => { deleteVersionAndVerify(currentVersion = "v5", deleteVersionText.deleteToastMessage(currentVersion = "v5")); cy.reload(); - releasedVersionAndVerify(currentVersion = "v3") + releasedVersionAndVerify(currentVersion = "v3"); editVersionAndVerify(currentVersion = "v3", newVersion = ["v5"], releasedVersionText.cannotUpdateReleasedVersionToastMessage); closeModal(commonText.closeButton); - cy.reload(); - closeModal(commonText.closeButton); deleteVersionAndVerify(currentVersion = "v3", releasedVersionText.cannotDeleteReleasedVersionToastMessage) navigateToCreateNewVersionModal(currentVersion = "v3"); diff --git a/cypress-tests/cypress/e2e/editor/widget/componentsBasicHappypath.cy.js b/cypress-tests/cypress/e2e/editor/widget/componentsBasicHappypath.cy.js index 2c85d08094..39352b2b01 100644 --- a/cypress-tests/cypress/e2e/editor/widget/componentsBasicHappypath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/componentsBasicHappypath.cy.js @@ -5,7 +5,7 @@ import { tableSelector } from "Selectors/table"; import { verifyComponent, deleteComponentAndVerify, - verifyComponentWithOutLabel + verifyComponentWithOutLabel, } from "Support/utils/basicComponents"; import { openAccordion, @@ -52,16 +52,20 @@ describe("Basic components", () => { editAndVerifyWidgetName("toggleswitch2"); verifyAndModifyParameter(commonWidgetText.parameterLabel, "label"); - cy.forceClickOnCanvas() + cy.forceClickOnCanvas(); cy.waitForAutoSave(); - cy.get('[data-cy="draggable-widget-toggleswitch2"] > .form-check-label').should('have.text', 'label') - + cy.get( + '[data-cy="draggable-widget-toggleswitch2"] > .form-check-label' + ).should("have.text", "label"); + cy.openInCurrentTab(commonWidgetSelector.previewButton); verifyComponent("toggleswitch2"); - cy.get('[data-cy="draggable-widget-toggleswitch2"] > .form-check-label').should('have.text', 'label') + cy.get( + '[data-cy="draggable-widget-toggleswitch2"] > .form-check-label' + ).should("have.text", "label"); cy.go("back"); - cy.wait('@appVersion') + cy.wait("@appVersion"); deleteComponentAndVerify("toggleswitch2"); cy.get(commonSelectors.editorPageLogo).click(); @@ -331,19 +335,24 @@ describe("Basic components", () => { cy.deleteApp(data.appName); }); -//needed fix + //needed fix it.skip("Should verify Custom Component", () => { cy.dragAndDropWidget("Custom Component", 50, 50); - cy.get('[data-cy="draggable-widget-customcomponent1"]').click({ force: true }); + cy.get('[data-cy="draggable-widget-customcomponent1"]').click({ + force: true, + }); cy.forceClickOnCanvas(); verifyComponent("customcomponent1"); openEditorSidebar("customcomponent1"); // editAndVerifyWidgetName("customcomponent2", ["Code"]); closeAccordions(["Code"]); - cy.get(commonWidgetSelector.WidgetNameInputField).type("{selectAll}{backspace}customcomponent2", {delay:30}); - cy.forceClickOnCanvas() - + cy.get(commonWidgetSelector.WidgetNameInputField).type( + "{selectAll}{backspace}customcomponent2", + { delay: 30 } + ); + cy.forceClickOnCanvas(); + cy.get(commonWidgetSelector.draggableWidget(name)).trigger("mouseover"); cy.get(commonWidgetSelector.widgetConfigHandle(name)) .click() @@ -411,13 +420,23 @@ describe("Basic components", () => { cy.deleteApp(data.appName); }); -//visible issue + //visible issue it.skip("Should verify Divider", () => { - verifyComponentWithOutLabel("Divider", "divider1", "divider2", data.appName) + verifyComponentWithOutLabel( + "Divider", + "divider1", + "divider2", + data.appName + ); }); it("Should verify File Picker", () => { - verifyComponentWithOutLabel("File Picker", "filepicker1", "filepicker2", data.appName) + verifyComponentWithOutLabel( + "File Picker", + "filepicker1", + "filepicker2", + data.appName + ); }); it("Should verify Form", () => { @@ -463,27 +482,28 @@ describe("Basic components", () => { }); it("Should verify Icon", () => { - verifyComponentWithOutLabel("Icon", "icon1", "icon2", data.appName) + verifyComponentWithOutLabel("Icon", "icon1", "icon2", data.appName); }); it("Should verify Iframe", () => { - verifyComponentWithOutLabel("Iframe", "iframe1", "iframe2", data.appName) + verifyComponentWithOutLabel("Iframe", "iframe1", "iframe2", data.appName); }); it.skip("Should verify Kamban", () => { - verifyComponentWithOutLabel("Kanban", "kanban1", "kanban2", data.appName) }); + verifyComponentWithOutLabel("Kanban", "kanban1", "kanban2", data.appName); + }); it("Should verify Link", () => { - verifyComponentWithOutLabel("Link", "link1", "link2", data.appName) + verifyComponentWithOutLabel("Link", "link1", "link2", data.appName); }); it("Should verify Map", () => { cy.dragAndDropWidget("Map", 50, 50); - cy.get("body").then($body => { + cy.get("body").then(($body) => { if ($body.find(".dismissButton").length > 0) { - cy.get('.dismissButton').click(); + cy.get(".dismissButton").click(); } - }) + }); verifyComponent("map1"); @@ -505,7 +525,7 @@ describe("Basic components", () => { }); it("Should verify Modal", () => { - verifyComponentWithOutLabel("Modal", "modal1", "modal2", data.appName) + verifyComponentWithOutLabel("Modal", "modal1", "modal2", data.appName); }); it("Should verify PDF", () => { @@ -530,35 +550,70 @@ describe("Basic components", () => { }); it("Should verify Pagination", () => { - verifyComponentWithOutLabel("Pagination", "pagination1", "pagination2", data.appName) + verifyComponentWithOutLabel( + "Pagination", + "pagination1", + "pagination2", + data.appName + ); }); it("Should verify QR Scanner", () => { - verifyComponentWithOutLabel("QR Scanner", "qrscanner1", "qrscanner2", data.appName) + verifyComponentWithOutLabel( + "QR Scanner", + "qrscanner1", + "qrscanner2", + data.appName + ); }); - it("Should verify Range Slider", () => { - verifyComponentWithOutLabel("Range Slider", "rangeslider1", "rangeslider2", data.appName) + it.skip("Should verify Range Slider", () => { + verifyComponentWithOutLabel( + "Range Slider", + "rangeslider1", + "rangeslider2", + data.appName + ); }); it("Should verify Rich Text Editor", () => { - verifyComponentWithOutLabel("Text Editor", "richtexteditor1", "richtexteditor2", data.appName) + verifyComponentWithOutLabel( + "Text Editor", + "richtexteditor1", + "richtexteditor2", + data.appName + ); }); it("Should verify Spinner", () => { - verifyComponentWithOutLabel("Spinner", "spinner1", "spinner2", data.appName); + verifyComponentWithOutLabel( + "Spinner", + "spinner1", + "spinner2", + data.appName + ); }); it("Should verify Statistics", () => { - verifyComponentWithOutLabel("Statistics", "statistics1", "statistics2", data.appName) + verifyComponentWithOutLabel( + "Statistics", + "statistics1", + "statistics2", + data.appName + ); }); it("Should verify Steps", () => { - verifyComponentWithOutLabel("Steps", "steps1", "steps2", data.appName) + verifyComponentWithOutLabel("Steps", "steps1", "steps2", data.appName); }); it("Should verify SVG Image", () => { - verifyComponentWithOutLabel("SVG Image", "svgimage1", "svgimage2", data.appName) + verifyComponentWithOutLabel( + "SVG Image", + "svgimage1", + "svgimage2", + data.appName + ); }); it("Should verify Tabs", () => { @@ -580,29 +635,49 @@ describe("Basic components", () => { deleteComponentAndVerify("tabs2"); cy.get(commonSelectors.editorPageLogo).click(); - cy.deleteApp(data.appName); + cy.deleteApp(data.appName); }); it("Should verify Tags", () => { - verifyComponentWithOutLabel("Tags", "tags1", "tags2", data.appName) + verifyComponentWithOutLabel("Tags", "tags1", "tags2", data.appName); }); it("Should verify Textarea", () => { - verifyComponentWithOutLabel("Textarea", "textarea1", "textarea2", data.appName) + verifyComponentWithOutLabel( + "Textarea", + "textarea1", + "textarea2", + data.appName + ); }); it("Should verify Timeline", () => { - verifyComponentWithOutLabel("Timeline", "timeline1", "timeline2", data.appName) + verifyComponentWithOutLabel( + "Timeline", + "timeline1", + "timeline2", + data.appName + ); }); it("Should verify Timer", () => { - verifyComponentWithOutLabel("Timer", "timer1", "timer2", data.appName) + verifyComponentWithOutLabel("Timer", "timer1", "timer2", data.appName); }); it("Should verify Tree Select", () => { - verifyComponentWithOutLabel("Tree Select", "treeselect1", "treeselect2", data.appName) + verifyComponentWithOutLabel( + "Tree Select", + "treeselect1", + "treeselect2", + data.appName + ); }); it("Should verify Vertical Divider", () => { - verifyComponentWithOutLabel("Vertical Divider", "verticaldivider1", "verticaldivider2", data.appName) - }); + verifyComponentWithOutLabel( + "Vertical Divider", + "verticaldivider1", + "verticaldivider2", + data.appName + ); + }); }); diff --git a/cypress-tests/cypress/e2e/editor/widget/modalHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/modalHappyPath.cy.js index c60c431a0f..ad0b07562a 100644 --- a/cypress-tests/cypress/e2e/editor/widget/modalHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/modalHappyPath.cy.js @@ -1,24 +1,38 @@ +import { commonSelectors, commonWidgetSelector } from "Selectors/common"; +import { buttonText } from "Texts/button"; import { fake } from "Fixtures/fake"; import { commonWidgetText } from "Texts/common"; -import { commonSelectors, commonWidgetSelector } from "Selectors/common"; -import { numberInputText } from "Texts/numberInput"; + +import { verifyControlComponentAction } from "Support/utils/button"; +import { selectEvent } from "Support/utils/events"; +import { + launchModal, + closeModal, + launchButton, + verifySize, + addAndVerifyColor, + typeOnFx, +} from "Support/utils/modal"; import { openAccordion, verifyAndModifyParameter, openEditorSidebar, verifyAndModifyToggleFx, - verifyComponentValueFromInspector, + addDefaultEventHandler, + addAndVerifyTooltip, + verifyComponentFromInspector, + verifyAndModifyStylePickerFx, + verifyWidgetColorCss, + selectColourFromColourPicker, + verifyLoaderColor, + fillBoxShadowParams, verifyBoxShadowCss, verifyLayout, verifyTooltip, editAndVerifyWidgetName, - addTextWidgetToVerifyValue, verifyPropertiesGeneralAccordion, verifyStylesGeneralAccordion, - randomNumber, - fillBoxShadowParams, - selectColourFromColourPicker, } from "Support/utils/commonWidget"; describe("Modal", () => { @@ -28,222 +42,204 @@ describe("Modal", () => { cy.dragAndDropWidget("Modal"); }); - it.only("should verify the properties of the number input widget", () => { + it("should verify the properties of the modal component", () => { const data = {}; data.appName = `${fake.companyName}-App`; + data.alertMessage = fake.randomSentence; data.widgetName = fake.widgetName; + data.customTitle = fake.randomSentence; data.tooltipText = fake.randomSentence; - data.randomNumber = randomNumber(10, 99); - data.minimumvalue = randomNumber(5, 10); - data.maximumValue = randomNumber(90, 99); + data.buttonText = fake.companyName; cy.renameApp(data.appName); - - openEditorSidebar('modal1'); - editAndVerifyWidgetName(data.widgetName, ["Options", "Properties", "Layout"]); - cy.get(`${ - commonWidgetSelector.draggableWidget(data.widgetName)}>button` - ).verifyVisibleElement("have.text", "Launch Modal"); - - openEditorSidebar(data.widgetName); - openAccordion(commonWidgetText.accordionProperties, [ - "Events", - "Properties", - "Layout", - ]); - verifyAndModifyParameter( - 'Title', - "fakeName" + launchModal("modal1"); + cy.get('[data-cy="modal-title"]').verifyVisibleElement( + "have.text", + "This title can be changed" ); - cy.forceClickOnCanvas(); - cy.get(`${ - commonWidgetSelector.draggableWidget(data.widgetName)}>button` - ).click(); + cy.get('[data-cy="modal-body"]').should("be.visible"); + cy.get('[data-cy="modal-close-button"]').click(); + cy.notVisible('[data-cy="modal-title"]'); - cy.get('.modal-header').verifyVisibleElement('have.text', "fakeName") - - cy.get('.modal-header> [class="cursor-pointer"]').click(); - - openEditorSidebar(data.widgetName); - openAccordion(commonWidgetText.accordionProperties, [ - "Events", + openEditorSidebar("modal1", ["Options", "Properties", "Layout"]); + editAndVerifyWidgetName(data.widgetName, [ + "Options", "Properties", "Layout", ]); + verifyComponentFromInspector(data.widgetName); + + openAccordion(commonWidgetText.accordionProperties); + verifyAndModifyParameter("Title", data.customTitle); + launchModal(data.widgetName); + cy.get('[data-cy="modal-title"]').verifyVisibleElement( + "have.text", + data.customTitle + ); + cy.get('[data-cy="modal-close-button"]').click(); verifyAndModifyToggleFx( - 'Loading State', - commonWidgetText.codeMirrorLabelFalse - ); + buttonText.loadingState, + commonWidgetText.codeMirrorLabelFalse + ); + launchModal(data.widgetName); + cy.get(".spinner-border").should("be.visible"); - cy.get(`${ - commonWidgetSelector.draggableWidget(data.widgetName)}>button` - ).click(); + cy.get( + commonWidgetSelector.parameterTogglebutton(buttonText.loadingState) + ).click(); + cy.notVisible(".spinner-border"); - cy.get('[class="spinner-border mt-5"]').should('be.visible'); + verifyAndModifyToggleFx( + "Hide title bar", + commonWidgetText.codeMirrorLabelFalse + ); + cy.notVisible('[data-cy="modal-title"]'); + cy.get('[data-cy="hide-title-bar-toggle-button"]').click(); + cy.get('[data-cy="modal-title"]').verifyVisibleElement( + "have.text", + data.customTitle + ); - verifyAndModifyToggleFx( - 'Hide title bar', - commonWidgetText.codeMirrorLabelFalse - ); - cy.notVisible('.modal-header') - cy.get('[data-cy="hide-title-bar-toggle-button"]').click() - cy.get('.modal-header').should('be.visible'); - - verifyAndModifyToggleFx( - 'Hide close button', - commonWidgetText.codeMirrorLabelFalse - ); - cy.notVisible('.modal-header> [class="cursor-pointer"]') - cy.get('[data-cy="hide-close-button-toggle-button"]').click() - cy.get('.modal-header> [class="cursor-pointer"]').should('be.visible'); + cy.realPress("Escape"); + cy.notVisible('[data-cy="modal-title"]'); - cy.realPress("Escape") - cy.notVisible('.modal-header') + verifyAndModifyToggleFx( + "Close on escape key", + commonWidgetText.codeMirrorLabelTrue + ); + launchModal(data.widgetName); - cy.get(`${ - commonWidgetSelector.draggableWidget(data.widgetName)}>button` - ).click(); - verifyAndModifyToggleFx( - 'Hide on escape', - commonWidgetText.codeMirrorLabelTrue - ); - cy.get('.modal-header> [class="cursor-pointer"]').should('be.visible'); + cy.realPress("Escape"); + cy.get('[data-cy="modal-title"]') + .verifyVisibleElement("have.text", data.customTitle) + .click(); + closeModal(data.widgetName); + launchModal(data.widgetName); + verifySize("Medium"); + verifySize("Large"); + verifySize("Small"); + verifyAndModifyToggleFx( + "Use default trigger button", + commonWidgetText.codeMirrorLabelTrue + ); + cy.get('[data-cy="modal-close-button"]').click(); + cy.notVisible(launchButton(data.widgetName)); + cy.get('[data-cy="use-default-trigger-button-toggle-button"]').click(); - // verifyComponentValueFromInspector(data.widgetName, data.randomNumber); - // cy.forceClickOnCanvas(); + cy.get( + '[data-cy="trigger-button-label-input-field"]' + ).clearAndTypeOnCodeMirror(data.buttonText); + cy.forceClickOnCanvas(); + cy.get(launchButton(data.widgetName)) + .verifyVisibleElement("have.text", data.buttonText) + .click(); - // openEditorSidebar(data.widgetName); - // openAccordion(commonWidgetText.accordionProperties, [ - // "Events", - // "Properties", - // "Layout", - // ]); - // verifyAndModifyParameter( - // commonWidgetText.labelMinimumValue, - // data.minimumvalue - // ); - // cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click(); - // cy.clearAndType( - // commonWidgetSelector.draggableWidget(data.widgetName), - // randomNumber(1, 4) - // ); - // cy.get( - // commonWidgetSelector.draggableWidget(data.widgetName) - // ).verifyVisibleElement("have.value", data.minimumvalue); + openAccordion(commonWidgetText.accordionEvents); + selectEvent("On open", "Show Alert"); + cy.get('[data-cy="modal-close-button"]').click(); + launchModal(data.widgetName); + cy.verifyToastMessage(commonSelectors.toastMessage, "Hello world!"); + cy.get('[data-cy="modal-close-button"]').click(); - // openEditorSidebar(data.widgetName); - // openAccordion(commonWidgetText.accordionProperties, [ - // "Events", - // "Properties", - // "Layout", - // ]); - // verifyAndModifyParameter( - // commonWidgetText.labelMaximumValue, - // data.maximumValue - // ); - // cy.clearAndType( - // commonWidgetSelector.draggableWidget(data.widgetName), - // randomNumber(100, 110) - // ); - // cy.get( - // commonWidgetSelector.draggableWidget(data.widgetName) - // ).verifyVisibleElement("have.value", data.maximumValue); + verifyLayout(data.widgetName); - // openEditorSidebar(data.widgetName); - // openAccordion(commonWidgetText.accordionProperties, [ - // "Events", - // "Properties", - // "Layout", - // ]); - // verifyAndModifyParameter( - // commonWidgetText.labelPlaceHolder, - // data.randomNumber - // ); - // cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click(); - // cy.get(commonWidgetSelector.draggableWidget(data.widgetName)) - // .invoke("attr", "placeholder") - // .should("contain", data.randomNumber); + cy.get(commonWidgetSelector.changeLayoutToDesktopButton).click(); + cy.get( + commonWidgetSelector.parameterTogglebutton( + commonWidgetText.parameterShowOnDesktop + ) + ).click(); - // verifyPropertiesGeneralAccordion(data.widgetName, data.tooltipText); - - // // verifyLayout(data.widgetName); - - // // cy.get(commonWidgetSelector.changeLayoutButton).click(); - // // cy.get( - // // commonWidgetSelector.parameterTogglebutton( - // // commonWidgetText.parameterShowOnDesktop - // // ) - // // ).click(); - - // cy.get(commonWidgetSelector.widgetDocumentationLink).should( - // "have.text", - // numberInputText.numberInputDocumentationLink - // ); + cy.get(commonWidgetSelector.widgetDocumentationLink).should( + "have.text", + "Modal documentation" + ); cy.get(commonSelectors.editorPageLogo).click(); cy.deleteApp(data.appName); }); - it("should verify the styles of the number Modal component", () => { + + it("should verify the styles of the modal widget", () => { const data = {}; data.appName = `${fake.companyName}-App`; - data.colourHex = fake.randomRgbaHex; data.boxShadowColor = fake.randomRgba; + data.colourHex = fake.randomRgbaHex; data.boxShadowParam = fake.boxShadowParam; + data.backgroundColor = fake.randomRgba; cy.renameApp(data.appName); - - openEditorSidebar(numberInputText.defaultWidgetName); + launchModal("modal1"); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); + addAndVerifyColor( + "Header background color", + "#ffffffff", + data.backgroundColor, + "[data-cy='modal-header']" + ); + + data.backgroundColor = fake.randomRgba; + addAndVerifyColor( + "Header title color", + "#000000", + data.backgroundColor, + "[data-cy='modal-header']", + "color" + ); + + data.backgroundColor = fake.randomRgba; + addAndVerifyColor( + "Body background color", + "#ffffffff", + data.backgroundColor, + "[data-cy='modal-body']" + ); + + data.backgroundColor = fake.randomRgba; + addAndVerifyColor( + "Trigger button background color", + "#4D72FA", + data.backgroundColor, + launchButton("modal1"), + "background-color" + ); + + data.backgroundColor = fake.randomRgba; + addAndVerifyColor( + "Trigger button text color", + "#ffffffff", + data.backgroundColor, + launchButton("modal1"), + "color" + ); + cy.get("[data-cy='modal-header']").realClick(); + verifyAndModifyToggleFx( commonWidgetText.parameterVisibility, commonWidgetText.codeMirrorLabelTrue ); - cy.get( - commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName) - ).should("not.be.visible"); - - cy.get( - commonWidgetSelector.parameterTogglebutton( - commonWidgetText.parameterVisibility - ) - ).click(); + cy.get('[data-cy="modal-close-button"]').click(); + cy.get(commonWidgetSelector.draggableWidget("modal1")).should( + "not.be.visible" + ); + cy.get(commonWidgetSelector.parameterTogglebutton("Visibility")).click(); verifyAndModifyToggleFx( commonWidgetText.parameterDisable, commonWidgetText.codeMirrorLabelFalse ); cy.waitForAutoSave(); - cy.get( - commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName) - ).should("have.attr", "disabled"); + cy.get(launchButton("modal1")).should("have.attr", "disabled"); - cy.get( - commonWidgetSelector.parameterTogglebutton( - commonWidgetText.parameterDisable - ) - ).click(); - - verifyAndModifyParameter( - commonWidgetText.parameterBorderRadius, - commonWidgetText.borderRadiusInput - ); - - cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click(); - cy.get( - commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName) - ).should("have.css", "border-radius", "20px"); - - verifyStylesGeneralAccordion( - numberInputText.defaultWidgetName, - data.boxShadowParam, - data.colourHex, - data.boxShadowColor, - 3 + cy.get(commonWidgetSelector.parameterTogglebutton("Disable")).click(); + launchModal("modal1"); + cy.get('[data-cy="modal-title"]').verifyVisibleElement( + "have.text", + "This title can be changed" ); cy.get(commonSelectors.editorPageLogo).click(); @@ -253,111 +249,171 @@ describe("Modal", () => { it("should verify the app preview", () => { const data = {}; data.appName = `${fake.companyName}-App`; - data.tooltipText = fake.randomSentence; - data.colourHex = fake.randomRgbaHex; - data.boxShadowColor = fake.randomRgba; - data.boxShadowParam = fake.boxShadowParam; - data.randomNumber = randomNumber(10, 99); - data.minimumvalue = randomNumber(5, 10); - data.maximumValue = randomNumber(90, 99); + data.bgColor = fake.randomRgba; + data.titleColor = fake.randomRgba; + data.bodyColor = fake.randomRgba; + data.buttonColor = fake.randomRgba; + data.buttonTextColor = fake.randomRgba; + data.customTitle = fake.randomSentence; + + cy.get(".close-svg > path").click(); + cy.dragAndDropWidget(commonWidgetText.toggleSwitch, 600, 50); + cy.get(".close-svg > path").click(); + cy.dragAndDropWidget(commonWidgetText.toggleSwitch, 600, 100); + cy.get(".close-svg > path").click(); + cy.dragAndDropWidget(commonWidgetText.toggleSwitch, 600, 150); + cy.get(".close-svg > path").click(); + cy.dragAndDropWidget(commonWidgetText.toggleSwitch, 600, 200); + cy.get(".close-svg > path").click(); + cy.dragAndDropWidget(commonWidgetText.toggleSwitch, 600, 250); + cy.get(".close-svg > path").click(); cy.renameApp(data.appName); - - openEditorSidebar(numberInputText.defaultWidgetName); - verifyAndModifyParameter( - commonWidgetText.labelDefaultValue, - data.randomNumber - ); - verifyAndModifyParameter( - commonWidgetText.labelMinimumValue, - data.minimumvalue - ); - verifyAndModifyParameter( - commonWidgetText.labelMaximumValue, - data.maximumValue - ); - verifyAndModifyParameter( - commonWidgetText.labelPlaceHolder, - data.randomNumber - ); - - verifyPropertiesGeneralAccordion( - numberInputText.defaultWidgetName, - data.tooltipText - ); - - openEditorSidebar(numberInputText.defaultWidgetName); + launchModal("modal1"); + verifyAndModifyParameter("Title", data.customTitle); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); - verifyAndModifyParameter( - commonWidgetText.parameterBorderRadius, - commonWidgetText.borderRadiusInput + + addAndVerifyColor( + "Header background color", + "#ffffffff", + data.bgColor, + "[data-cy='modal-header']" ); - cy.forceClickOnCanvas(); - cy.waitForAutoSave(); - cy.reload(); - - openEditorSidebar(numberInputText.defaultWidgetName); - cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); - openAccordion(commonWidgetText.accordionGenaral, []); - cy.get(commonWidgetSelector.boxShadowColorPicker).click(); - - fillBoxShadowParams( - commonWidgetSelector.boxShadowDefaultParam, - data.boxShadowParam + addAndVerifyColor( + "Header title color", + "#000000", + data.titleColor, + "[data-cy='modal-header']", + "color" ); - selectColourFromColourPicker( - commonWidgetText.boxShadowColor, - data.boxShadowColor, - 3 - ); - addTextWidgetToVerifyValue("components.numberinput1.value"); + addAndVerifyColor( + "Body background color", + "#ffffffff", + data.bodyColor, + "[data-cy='modal-body']" + ); + + addAndVerifyColor( + "Trigger button background color", + "#4D72FA", + data.buttonColor, + launchButton("modal1"), + "background-color" + ); + + addAndVerifyColor( + "Trigger button text color", + "#ffffffff", + data.buttonTextColor, + launchButton("modal1"), + "color" + ); + cy.get("[data-cy='modal-header']").realClick(); + + typeOnFx( + commonWidgetText.parameterVisibility, + "{{components.toggleswitch1.value" + ); + cy.get("[data-cy='modal-header']").realClick(); + typeOnFx( + commonWidgetText.parameterDisable, + "{{components.toggleswitch2.value" + ); + cy.get(".close-svg > path").click(); + cy.get("[data-cy='modal-header']").realClick(); + + typeOnFx("Loading State", "{{components.toggleswitch3.value"); + cy.get("[data-cy='modal-header']").realClick(); + + typeOnFx("Hide title bar", "{{components.toggleswitch4.value"); + cy.get("[data-cy='modal-header']").realClick(); + + typeOnFx("Hide close button", "{{components.toggleswitch5.value"); + cy.get("[data-cy='modal-header']").realClick(); cy.waitForAutoSave(); cy.openInCurrentTab(commonWidgetSelector.previewButton); + cy.wait(2000); - cy.get( - commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName) - ).verifyVisibleElement("have.value", data.randomNumber); + cy.notVisible(launchButton("modal1")); + cy.get(commonWidgetSelector.draggableWidget("toggleswitch1")) + .find(".form-check-input") + .click(); + cy.get(launchButton("modal1")).should("be.visible"); - cy.clearAndType( - commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName), - randomNumber(1, 4) + cy.get(commonWidgetSelector.draggableWidget("toggleswitch2")) + .find(".form-check-input") + .click(); + cy.get(launchButton("modal1")).should("have.attr", "disabled"); + cy.get(commonWidgetSelector.draggableWidget("toggleswitch2")) + .find(".form-check-input") + .click(); + cy.get(commonWidgetSelector.draggableWidget("toggleswitch3")) + .find(".form-check-input") + .click(); + launchModal("modal1"); + cy.get(".spinner-border").should("be.visible"); + cy.realPress("Escape"); + + cy.get(commonWidgetSelector.draggableWidget("toggleswitch3")) + .find(".form-check-input") + .click(); + + cy.get(commonWidgetSelector.draggableWidget("toggleswitch4")) + .find(".form-check-input") + .click(); + launchModal("modal1"); + cy.notVisible('[data-cy="modal-title"]'); + cy.realPress("Escape"); + + cy.get(commonWidgetSelector.draggableWidget("toggleswitch4")) + .find(".form-check-input") + .click(); + launchModal("modal1"); + verifyWidgetColorCss( + "[data-cy='modal-header']", + "background-color", + data.bgColor, + true ); - cy.get( - commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName) - ).verifyVisibleElement("have.value", data.minimumvalue); - cy.clearAndType( - commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName), - randomNumber(100, 110) + verifyWidgetColorCss( + "[data-cy='modal-header']", + "color", + data.titleColor, + true ); - cy.get( - commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName) - ).verifyVisibleElement("have.value", data.maximumValue); - cy.get( - commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName) - ) - .invoke("attr", "placeholder") - .should("contain", data.randomNumber); - - verifyTooltip( - commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName), - data.tooltipText - ); - cy.get( - commonWidgetSelector.draggableWidget(commonWidgetText.text1) - ).verifyVisibleElement("have.text", data.maximumValue); - - cy.get( - commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName) - ).should("have.css", "border-radius", "20px"); - verifyBoxShadowCss( - numberInputText.defaultWidgetName, - data.boxShadowColor, - data.boxShadowParam + verifyWidgetColorCss( + "[data-cy='modal-body']", + "background-color", + data.bodyColor, + true ); - cy.get(commonSelectors.viewerPageLogo).click(); - cy.deleteApp(data.appName); + cy.realPress("Escape"); + verifyWidgetColorCss( + launchButton("modal1"), + "color", + data.buttonTextColor, + true + ); + verifyWidgetColorCss( + launchButton("modal1"), + "background-color", + data.buttonColor, + true + ); + launchModal("modal1"); + + cy.get('[data-cy="modal-title"]').verifyVisibleElement( + "have.text", + data.customTitle + ); + cy.realPress("Escape"); + cy.get(commonWidgetSelector.draggableWidget("toggleswitch5")) + .find(".form-check-input") + .click(); + launchModal("modal1"); + cy.notVisible('[data-cy="modal-close-button"]'); }); }); diff --git a/cypress-tests/cypress/e2e/exportImport/export.cy.js b/cypress-tests/cypress/e2e/exportImport/export.cy.js index 13d3adf858..4e936b3054 100644 --- a/cypress-tests/cypress/e2e/exportImport/export.cy.js +++ b/cypress-tests/cypress/e2e/exportImport/export.cy.js @@ -91,7 +91,10 @@ describe("App Export Functionality", () => { cy.get(appVersionSelectors.appVersionMenuField) .should("be.visible") .click(); - createNewVersion((otherVersions = ["v2"])); + createNewVersion(otherVersions = ["v2"], currentVersion = "v1"); + cy.wait(500); + cy.dragAndDropWidget("Toggle Switch", 50, 50); + cy.waitForAutoSave(); cy.get(appVersionSelectors.currentVersionField((otherVersions = "v2"))) .should("be.visible") .invoke("text") diff --git a/cypress-tests/cypress/e2e/exportImport/import.cy.js b/cypress-tests/cypress/e2e/exportImport/import.cy.js index 2a0503f587..f4c0b688cc 100644 --- a/cypress-tests/cypress/e2e/exportImport/import.cy.js +++ b/cypress-tests/cypress/e2e/exportImport/import.cy.js @@ -51,7 +51,7 @@ describe("App Import Functionality", () => { force: true, }); cy.verifyToastMessage( - commonSelectors.oldToastMessage, + commonSelectors.toastMessage, importText.couldNotImportAppToastMessage ); @@ -60,7 +60,7 @@ describe("App Import Functionality", () => { }); cy.get(".driver-close-btn").click(); cy.verifyToastMessage( - commonSelectors.oldToastMessage, + commonSelectors.toastMessage, importText.appImportedToastMessage ); cy.get(commonSelectors.appNameInput).verifyVisibleElement( @@ -104,7 +104,7 @@ describe("App Import Functionality", () => { force: true, }); cy.verifyToastMessage( - commonSelectors.oldToastMessage, + commonSelectors.toastMessage, importText.appImportedToastMessage ); cy.get( @@ -132,10 +132,11 @@ describe("App Import Functionality", () => { cy.reload(); navigateToAppEditor(data.appReName); + cy.wait(500); cy.get(appVersionSelectors.appVersionMenuField) .should("be.visible") .click(); - createNewVersion((otherVersions = ["v2"])); + createNewVersion(otherVersions = ["v2"], currentVersion = "v1"); cy.get(appVersionSelectors.currentVersionField((otherVersions = "v2"))) .should("be.visible") .click() @@ -174,7 +175,7 @@ describe("App Import Functionality", () => { } ); cy.verifyToastMessage( - commonSelectors.oldToastMessage, + commonSelectors.toastMessage, importText.appImportedToastMessage ); cy.get(appVersionSelectors.appVersionMenuField).click(); diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 0b225b4e50..4ee78384f2 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -92,7 +92,7 @@ Cypress.Commands.add("waitForAutoSave", () => { Cypress.Commands.add("createApp", (appName) => { cy.get("body").then(($title) => { if ($title.text().includes(commonText.introductionMessage)) { - cy.get(commonSelectors.emptyAppCreateButton).click(); + cy.get(commonSelectors.emptyAppCreateButton).eq(0).click(); } else { cy.get(commonSelectors.appCreateButton).click(); } @@ -237,6 +237,12 @@ Cypress.Commands.add("notVisible", (dataCy) => { cy.get(dataCy).should("not.be.visible"); } }); + const log = Cypress.log({ + name: 'notVisible', + displayName: 'Not Visible', + message: dataCy + }) + }); Cypress.Commands.add("resizeWidget", (widgetName, x, y) => { diff --git a/cypress-tests/cypress/support/utils/common.js b/cypress-tests/cypress/support/utils/common.js index a3648bee04..c5f0e6be8e 100644 --- a/cypress-tests/cypress/support/utils/common.js +++ b/cypress-tests/cypress/support/utils/common.js @@ -78,7 +78,7 @@ export const navigateToAppEditor = (appName) => { .trigger("mousehover") .trigger("mouseenter") .find(commonSelectors.editButton) - .click(); + .click({force:true}); //cy.wait("@appEditor"); }; @@ -152,8 +152,7 @@ export const manageUsersPagination = (email) => { }; export const searchUser = (email) => { - cy.clearAndType(commonSelectors.emailFilterInput, email); - cy.get(commonSelectors.filterButton).click(); + cy.clearAndType(commonSelectors.inputUserSearch, email); }; export const createWorkspace = (workspaceName) => { diff --git a/cypress-tests/cypress/support/utils/commonWidget.js b/cypress-tests/cypress/support/utils/commonWidget.js index a5297afa0b..237d2c034d 100644 --- a/cypress-tests/cypress/support/utils/commonWidget.js +++ b/cypress-tests/cypress/support/utils/commonWidget.js @@ -71,7 +71,8 @@ export const addDefaultEventHandler = (message) => { .click(); cy.get(commonWidgetSelector.eventHandlerCard).click(); cy.get(commonWidgetSelector.alertMessageInputField) - .find('[data-cy*="-input-field"]').eq(0) + .find('[data-cy*="-input-field"]') + .eq(0) .clearAndTypeOnCodeMirror(message); }; @@ -240,9 +241,16 @@ export const verifyAndModifyStylePickerFx = ( }); }; -export const verifyWidgetColorCss = (widgetName, cssProperty, color) => { +export const verifyWidgetColorCss = ( + widgetName, + cssProperty, + color, + innerProp = false +) => { cy.forceClickOnCanvas(); - cy.get(commonWidgetSelector.draggableWidget(widgetName)).should( + cy.get( + innerProp ? widgetName : commonWidgetSelector.draggableWidget(widgetName) + ).should( "have.css", cssProperty, `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3] / 100})` diff --git a/cypress-tests/cypress/support/utils/database.js b/cypress-tests/cypress/support/utils/database.js index 626c84218a..90909cf2b0 100644 --- a/cypress-tests/cypress/support/utils/database.js +++ b/cypress-tests/cypress/support/utils/database.js @@ -17,10 +17,10 @@ import { commonText } from "Texts/common"; export const verifyAllElementsOfPage = () => { cy.get(databaseSelectors.addTableButton).should("be.visible"); - cy.get(databaseSelectors.tablePageHeader).verifyVisibleElement( - "have.text", - databaseText.tablePageHeader - ); + // cy.get(databaseSelectors.tablePageHeader).verifyVisibleElement( + // "have.text", + // databaseText.tablePageHeader + // ); cy.get(databaseSelectors.doNotHaveTableText).verifyVisibleElement( "have.text", databaseText.doNotHaveTableText diff --git a/cypress-tests/cypress/support/utils/events.js b/cypress-tests/cypress/support/utils/events.js new file mode 100644 index 0000000000..4eb537a327 --- /dev/null +++ b/cypress-tests/cypress/support/utils/events.js @@ -0,0 +1,28 @@ +export const selectEvent = (event, action) => { + cy.get('[data-cy="add-event-handler"]').click() + cy.get('[data-cy="event-handler"]').eq(0).click() + cy.get('[data-cy="event-selection"]') + .click() + .find("input") + .type(`{selectAll}{backspace}${event}{enter}`); + cy.get('[data-cy="action-selection"]') + .click() + .find("input") + .type(`{selectAll}{backspace}${action}{enter}`); +}; + +export const selectCSA = ( + component, + componentAction, + dbounce = `{selectAll}{backspace}` +) => { + cy.get('[data-cy="action-options-component-selection-field"]') + .click() + .find("input") + .type(`{selectAll}{backspace}${component}`); + cy.get('[data-cy="action-options-action-selection-field"]') + .click() + .find("input") + .type(`{selectAll}{backspace}${componentAction}`); + cy.get('[data-cy="-input-field"]').type(`{selectAll}{backspace}${debounce}`); +}; diff --git a/cypress-tests/cypress/support/utils/exportImport.js b/cypress-tests/cypress/support/utils/exportImport.js index cb6a870544..3adb27da2a 100644 --- a/cypress-tests/cypress/support/utils/exportImport.js +++ b/cypress-tests/cypress/support/utils/exportImport.js @@ -70,7 +70,7 @@ export const createNewVersion = (newVersion = [], version) => { export const clickOnExportButtonAndVerify = (buttonText, appName) => { cy.get(commonSelectors.buttonSelector(buttonText)).click(); - +cy.wait(1000); cy.exec("ls ./cypress/downloads/").then((result) => { const downloadedAppExportFileName = result.stdout.split("\n")[0]; expect(downloadedAppExportFileName).to.have.string(appName.toLowerCase()); diff --git a/cypress-tests/cypress/support/utils/manageUsers.js b/cypress-tests/cypress/support/utils/manageUsers.js index 0daf568eca..0251b70afd 100644 --- a/cypress-tests/cypress/support/utils/manageUsers.js +++ b/cypress-tests/cypress/support/utils/manageUsers.js @@ -1,19 +1,33 @@ import { path } from "Texts/common"; -import { commonSelectors } from "Selectors/common"; -import { usersText } from "Texts/manageUsers"; +import { commonSelectors } from "Selectors/common" +import { usersText } from "Texts/manageUsers" import { usersSelector } from "Selectors/manageUsers"; import { ssoSelector } from "Selectors/manageSSO"; import { ssoText } from "Texts/manageSSO"; import * as common from "Support/utils/common"; -import { commonText } from "../../constants/texts/common"; +import { commonText } from "Texts/common"; export const manageUsersElements = () => { + + cy.get(commonSelectors.breadcrumbTitle).should(($el) => { + expect($el.contents().first().text().trim()).to.eq( + commonText.breadcrumbworkspaceSettingTitle + ); + }); + cy.get(commonSelectors.breadcrumbPageTitle).verifyVisibleElement( "have.text",usersText.breadcrumbUsersPageTitle); + for (const element in usersSelector.usersElements) { cy.get(usersSelector.usersElements[element]).verifyVisibleElement( "have.text", usersText.usersElements[element] ); } + cy.get(usersSelector.usersPageTitle).should(($el) => { + expect($el.contents().last().text().trim()).to.eq( + usersText.usersPageTitle + ); + }); + cy.get(commonSelectors.inputUserSearch).should("be.visible") common.searchUser(usersText.adminUserEmail); cy.contains("td", usersText.adminUserEmail) .parent() @@ -35,51 +49,56 @@ export const manageUsersElements = () => { usersText.adminUserState ); }); - cy.get(commonSelectors.emailFilterInput).should("be.visible"); - cy.get(commonSelectors.firstNameFilterInput).should("be.visible"); - cy.get(commonSelectors.lastNameFilterInput).should("be.visible"); - cy.get(commonSelectors.clearFilterButton).should("be.visible"); - cy.get(commonSelectors.userStatusSelect).should("be.visible"); + cy.get(usersSelector.userFilterInput).should("be.visible"); - cy.get(usersSelector.inviteUserButton) - .verifyVisibleElement("have.text", usersText.inviteUserButton) + cy.get(usersSelector.buttonAddUsers) + .verifyVisibleElement("have.text", usersText.buttonAddUsers) .click(); - cy.get(usersSelector.cardTitle).verifyVisibleElement( - "have.text", - usersText.cardTitle - ); - cy.get(usersSelector.firstNameInput).should("be.visible"); - cy.get(usersSelector.lastNameInput).should("be.visible"); - cy.get(usersSelector.emailLabel).verifyVisibleElement( - "have.text", - usersText.emailLabel - ); - cy.get(usersSelector.lastNameInput).should("be.visible"); - cy.get(usersSelector.cancelButton).verifyVisibleElement( - "have.text", - usersText.cancelButton - ); - cy.get(usersSelector.createUserButton).verifyVisibleElement( - "have.text", - usersText.createUserButton - ); - cy.get(usersSelector.cancelButton).click(); + cy.get(usersSelector.buttonInviteWithEmail).verifyVisibleElement( "have.text",usersText.buttonInviteWithEmail); + cy.get(usersSelector.buttonUploadCsvFile).verifyVisibleElement("have.text",usersText.buttonUploadCsvFile); + cy.get(usersSelector.addUsersCardTitle).verifyVisibleElement("have.text",usersText.addUsersCardTitle); + + cy.get(commonSelectors.labelFullNameInput).verifyVisibleElement("have.text",commonText.labelFullNameInput); + cy.get(commonSelectors.inputFieldFullName).should("be.visible"); + cy.get(commonSelectors.labelEmailInput).verifyVisibleElement("have.text",commonText.labelEmailInput); + cy.get(commonSelectors.inputFieldEmailAddress).should("be.visible"); + + cy.get(commonSelectors.cancelButton).verifyVisibleElement("have.text",usersText.cancelButton); + cy.get(usersSelector.buttonInviteUsers).verifyVisibleElement( + "have.text", + usersText.buttonInviteUsers + ); + cy.get(commonSelectors.cancelButton).click(); + cy.get(usersSelector.addUsersCardTitle).should("not.exist") + + cy.get(usersSelector.buttonAddUsers).click(); + cy.get(commonSelectors.closeButton).click(); + cy.get(usersSelector.addUsersCardTitle).should("not.exist") + + cy.get(usersSelector.buttonAddUsers).click(); + cy.get(usersSelector.addUsersCardTitle).verifyVisibleElement("have.text", usersText.addUsersCardTitle); + cy.get(usersSelector.buttonUploadCsvFile).click(); + + cy.get(usersSelector.helperTextBulkUpload).verifyVisibleElement("have.text", usersText.helperTextBulkUpload); + cy.get(usersSelector.buttonDownloadTemplate).verifyVisibleElement("have.text", usersText.buttonDownloadTemplate); + cy.get(usersSelector.iconBulkUpload).should("be.visible") + cy.get(usersSelector.helperTextSelectFile).verifyVisibleElement("have.text", usersText.helperTextSelectFile); + cy.get(usersSelector.helperTextDropFile).verifyVisibleElement("have.text", usersText.helperTextDropFile); + cy.get(usersSelector.inputFieldBulkUpload).should("exist") + cy.get(usersSelector.buttonUploadUsers).verifyVisibleElement("have.text", usersText.buttonUploadUsers); + + + - cy.get(usersSelector.inviteBulkUserButton).verifyVisibleElement("have.text",usersText.inviteBulkUserButton).click(); - cy.get(usersSelector.bulkUserUploadPageTitle).verifyVisibleElement("have.text",usersText.bulkUserUploadPageTitle); - cy.get(usersSelector.bulkUSerUploadInput).should("be.visible"); - cy.get(usersSelector.downloadTemplateButton).verifyVisibleElement("have.text",usersText.downloadTemplateButton); - cy.get(usersSelector.cancelButton).verifyVisibleElement("have.text",usersText.cancelButton); }; -export const inviteUser = (firstName, lastName, email) => { - cy.get(usersSelector.inviteUserButton).click(); - cy.clearAndType(usersSelector.firstNameInput, firstName); - cy.clearAndType(usersSelector.lastNameInput, lastName); - cy.clearAndType(usersSelector.emailInput, email); +export const inviteUser = (firstName, email) => { + cy.get(usersSelector.buttonAddUsers).click(); + cy.clearAndType(commonSelectors.inputFieldFullName, firstName); + cy.clearAndType(commonSelectors.inputFieldEmailAddress, email); - cy.get(usersSelector.createUserButton).click(); + cy.get(usersSelector.buttonInviteUsers).click(); cy.verifyToastMessage( commonSelectors.toastMessage, usersText.userCreatedToast @@ -91,7 +110,7 @@ export const inviteUser = (firstName, lastName, email) => { cy.contains("td", email) .parent() .within(() => { - cy.get("td img").click(); + cy.get(usersSelector.copyInvitationLink).click(); }); cy.verifyToastMessage( commonSelectors.toastMessage, @@ -100,15 +119,13 @@ export const inviteUser = (firstName, lastName, email) => { cy.get("@copyToClipboardPrompt").then((prompt) => { common.logout(); cy.visit(prompt.args[0][1]); - cy.url().should("include", path.confirmInvite); }); }; -export const addNewUser = (firstName, lastName, email) => { +export const addNewUser = (firstName, email) => { cy.intercept("POST", "/api/organization_users").as("appLibrary"); - cy.clearAndType(usersSelector.firstNameInput, firstName); - cy.clearAndType(usersSelector.lastNameInput, lastName); - cy.clearAndType(usersSelector.emailInput, email); + cy.clearAndType(commonSelectors.inputFieldFullName, firstName); + cy.clearAndType(commonSelectors.inputFieldEmailAddress, email); cy.get(usersSelector.createUserButton).click(); cy.wait("@appLibrary").then((res) => { @@ -121,7 +138,6 @@ export const addNewUser = (firstName, lastName, email) => { }; export const confirmInviteElements = () => { - cy.url().should("include", '/confirm'); cy.get(commonSelectors.invitePageHeader).verifyVisibleElement( "have.text", commonText.invitePageHeader diff --git a/cypress-tests/cypress/support/utils/modal.js b/cypress-tests/cypress/support/utils/modal.js new file mode 100644 index 0000000000..df515e2c27 --- /dev/null +++ b/cypress-tests/cypress/support/utils/modal.js @@ -0,0 +1,58 @@ +import { commonWidgetSelector } from "Selectors/common"; +import { + verifyAndModifyStylePickerFx, + selectColourFromColourPicker, + verifyWidgetColorCss, +} from "Support/utils/commonWidget"; + +export const launchModal = (componentName) => { + cy.get(launchButton(componentName)).click(); +}; + +export const launchButton = (componentName) => { + return `[data-cy="draggable-widget-${componentName + .toLowerCase() + .replace(" ", "-")}-launch-button"]`; +}; + +export const verifySize = (size) => { + const className = { + Small: "sm", + Medium: "lg", + Large: "xl", + }; + cy.get('[data-cy="dropdown-modal-size"]') + .click() + .find("input") + .type(`{selectAll}{backspace}${size}{enter}`); + cy.get( + `[class="modal-dialog modal-${className[size]} modal-dialog-scrollable"]` + ).should("exist"); +}; + +export const closeModal = (componentName) => { + cy.get('[data-cy="modal-close-button"]').realClick(); +}; + +export const addAndVerifyColor = ( + section, + defaultColor, + color, + dataCy, + type = "background-color" +) => { + verifyAndModifyStylePickerFx(section, defaultColor, "data.colourHex"); + cy.get(commonWidgetSelector.parameterFxButton(section)).click(); + + selectColourFromColourPicker(section, color); + verifyWidgetColorCss(dataCy, type, color, true); + cy.get(dataCy).realClick(); + cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); +}; + +export const typeOnFx = (fx, data) => { + cy.get(commonWidgetSelector.parameterFxButton(fx)).eq(1).realClick(); + cy.get(commonWidgetSelector.parameterInputField(fx)).clearAndTypeOnCodeMirror( + data + ); +}; diff --git a/cypress-tests/cypress/support/utils/version.js b/cypress-tests/cypress/support/utils/version.js index 4b1fc5a3bd..c0689507c4 100644 --- a/cypress-tests/cypress/support/utils/version.js +++ b/cypress-tests/cypress/support/utils/version.js @@ -9,7 +9,8 @@ import { verifyComponent } from "Support/utils/basicComponents"; export const navigateToCreateNewVersionModal = (value) => { cy.get(appVersionSelectors.currentVersionField(value)).should("be.visible").click(); - cy.contains(appVersionText.createNewVersion).should("be.visible").click(); + cy.contains(appVersionText.createNewVersion).should("be.visible"); + cy.contains(appVersionText.createNewVersion).click(); } export const navigateToEditVersionModal = (value) => { @@ -41,6 +42,12 @@ export const verifyElementsOfCreateNewVersionModal = (version = []) => { export const editVersionAndVerify = (currentVersion, newVersion = [], toastMessageText) => { cy.reload(); + cy.get(appVersionSelectors.currentVersionField(currentVersion)).then(($ele) => { + if ($ele.hasClass("color-light-green")) { + cy.contains(releasedVersionText.releasedModalText).should("be.visible"); + closeModal(commonText.closeButton); + } + }) navigateToEditVersionModal(currentVersion) cy.get(editVersionSelectors.versionNameInputField).verifyVisibleElement("have.value", currentVersion); @@ -49,6 +56,7 @@ export const editVersionAndVerify = (currentVersion, newVersion = [], toastMessa newVersion[0] ); cy.get(editVersionSelectors.saveButton).click(); + cy.wait(500); cy.verifyToastMessage( commonSelectors.toastMessage, toastMessageText @@ -100,6 +108,7 @@ export const releasedVersionAndVerify = (currentVersion) => { appVersionSelectors.versionNameInputField ); cy.contains(releasedVersionText.releasedModalText).should("be.visible"); + cy.wait(500); closeModal(commonText.closeButton); cy.get(appVersionSelectors.currentVersionField(currentVersion)).should("have.class", "color-light-green"); }; diff --git a/docs/docs/Enterprise/superadmin.md b/docs/docs/Enterprise/superadmin.md index 63262b98e9..0af8d92a66 100644 --- a/docs/docs/Enterprise/superadmin.md +++ b/docs/docs/Enterprise/superadmin.md @@ -17,6 +17,7 @@ The user details entered while setting up ToolJet will have Super Admin privileg | Manage Groups in their workspace (Create Group/Add or Delete Users from groups/ Modify Group Permissions) | ✅ | ✅ | | Manage SSO in their workspace | ✅ | ✅ | | Manage Workspace Variables in their workspace | ✅ | ✅ | +| [Manage Global datasources for the user group in their workspace](/docs/next/data-sources/overview#permissions) | ✅ | ✅ | | [Access any user's personal workspace (create, edit or delete apps)](#access-any-workspace) | ❌ | ✅ | | [Archive Admin or any user of any workspace](#archiveunarchive-users) | ❌ | ✅ | | [Access any user's ToolJet database (create, edit or delete database)](#access-tooljet-db-in-any-workspace) | ❌ | ✅ | diff --git a/docs/docs/data-sources/overview.md b/docs/docs/data-sources/overview.md index ec7527f8af..64eefc507c 100644 --- a/docs/docs/data-sources/overview.md +++ b/docs/docs/data-sources/overview.md @@ -3,9 +3,13 @@ id: overview title: Overview --- -# Datasources : Overview +# Global Datasources : Overview -Datasources pull in and push data to any source including databases, external APIs, or services. +Global datasources pull in and push data to any source including databases, external APIs, or services. Once a global datasource is connected to a workspace, the connection can be shared with any app of that workspace. + +:::caution +Global datasources are available only on **ToolJet version 2.3.0 and above**. +:::
@@ -13,30 +17,115 @@ Datasources pull in and push data to any source including databases, external AP
-## Connecting datasources +## Connecting global datasources -1. After logging in to ToolJet, create a new app from the dashboard +1. From the ToolJet dashboard, go to the **global datasources page** from the left sidebar. +
-2. There are two ways for connecting a datasource. You can connect from: - 1. **Left-sidebar**: On the left sidebar, click on the `datasource` icon and then click on the `+ add datasource` button + Datasources: Overview -
+
- Datasources: Overview +2. Click on the **Add new datasource** button, a modal will pop-up with all the available global datasources. +
-
+ Datasources: Overview - 2. **Query Panel**: Go to the query panel at the bottom, click on the `+Add` button and then click `Add datasource` button +
-
+3. Select the datasource, enter the **Credentials** and **Save** the datasource. +
- Datasources: Overview + Datasources: Overview -
+
-3. Follow the steps in the **[Datasource Library](/docs/data-sources/airtable)** specific to the desired datasource +4. Now, go back to the dashboard, create a new app, and the datasource will be available on the query panel under **Global Datasources**. Added datasources will be available on any of the **existing** or the **new applications**. +
+ + Datasources: Overview + +
+ +5. You can now create queries of the connected global datasource. From the queries, you'll be able to switch to **different connections** of the same datasource if there are more than one connections created. +
+ + Datasources: Overview + +
+ +## Changing scope of datasources of an app created on older versions of ToolJet + +On ToolJet versions below 2.3.0, the datasource connection was made from within the individual apps. To make it backward compatible, we added an option to change the scope of the datasources and make it global datasource. + +1. If you open an app created on previos versions of ToolJet, you'll find the datasource manager on the left sidebar of the App Builder. +
+ + Datasources: Overview + +
+ +2. Click on the kebab menu next to the connected datasource, select the **change scope** option. +
+ + Datasources: Overview + +
+ +3. Once you change the scope of the datasource and make it global, you'll see that the **datasource manager** is removed from the left sidebar and now you'll find the datasource on the **query panel** under Global Datasources. You can now configure the datasource fromt the Global Datasource page on the **dashboard**. +
+ + Datasources: Overview + +
+ + +## Default datasources + +By default, 4 datasources will be available on every app on ToolJet: +- **[ToolJet Database](/docs/tooljet-database/)** +- **[RestAPI](/docs/data-sources/restapi/)** +- **[Run JavaScript Query](/docs/data-sources/run-js/)** +- **[Run Python Query](/docs/data-sources/run-py/)** + +
+ + Datasources: Overview + +
+ +## Permissions + +Only **Admins** and **[Super Admins](/docs/Enterprise/superadmin)** of the workspace can change the **[Permissions](/docs/tutorial/manage-users-groups#group-properties)** for Global Datasource. + +From **Workspace Settings** -> **Groups Settings**, Admins and Super Admins can set the permission for a user group to: + +- **Create** and **Delete** datasources onto that workspace. If **Create** permission is enabled then the users can add new global datasources and **edit** the datasources as well but cannot **delete** it, and if only **Delete** permission is set then the users of the group will only be able to delete the connected datasources on the workspace. +
+ + Datasources: Overview + +
+ + - If any of the permission(Create or Delete) is not enabled for a user group then the users of the group will get an error toast when they try to Add or Delete the global datasource. +
+ + Datasources: Overview + +
+ +- **View** or **Edit** allowed global datasources from the **Datasources** tab. If only **View** permission is set then the users of the group will only be able to connect to the allowed datasource, and if only **Edit** permission is set then the users of the group will be able to update the credentials of the allowed datasources. +
+ + Datasources: Overview + +
+ + - If any of the permission(View or Edit) is not enabled for a user group then the users of the group will get an error toast when they try to Add or Delete the global datasource. +
+ + Datasources: Overview + +
-:::info -ToolJet allows you to transform the data returned by datasources using **[Transformations](/docs/tutorial/transformations)** -::: diff --git a/docs/docs/data-sources/smtp.md b/docs/docs/data-sources/smtp.md index 120f2b1d08..9f09a14ac4 100644 --- a/docs/docs/data-sources/smtp.md +++ b/docs/docs/data-sources/smtp.md @@ -5,18 +5,24 @@ title: SMTP # SMTP -SMTP plugin can connect ToolJet applications to **SMTP servers** for sending emails. +The SMTP datasource facilitates the connection between ToolJet applications and email servers, enabling the apps to send emails. ## Connection -A SMTP server can be connected with the following credentails: -- **Host** -- **Port** -- **User** +To connect to an SMTP server, the following credentials are typically required: + +- **Host** +- **Port** +- **Username** - **Password** -:::info -You can also test your connection before saving the configuration by clicking on `Test Connection` button. +:::tip Finding configuration details: +The SMTP configuration details like host and port can usually be obtained from your email service provider. Here are some general settings for the most commonly used email providers: +- **Gmail**: `Host`: smtp.gmail.com; `Port`: 587 or 465 (SSL); `Username`: your full Gmail email address; `Password`: your Gmail password. +- **Yahoo Mail**: `Host`: smtp.mail.yahoo.com; `Port`: 465 (SSL); `Username`: your Yahoo Mail email address; `Password`: your Yahoo Mail password. +- **Outlook.com/Hotmail**: `Host`: smtp.office365.com; `Port`: 587 or 465 (SSL); `Username`: your Outlook.com/Hotmail email address; `Password`: your Outlook.com/Hotmail password. + +Before saving the configuration, it's possible to test the connection by clicking the "Test Connection" button. :::
@@ -27,22 +33,25 @@ You can also test your connection before saving the configuration by clicking on ## Querying SMTP -Go to the query manager at the bottom panel of the editor and click on the `+` button on the left to create a new query. Select `SMTP` from the datasource dropdown. +To create a query for sending an email, follow these steps: -To create a query for sending email, you will need to provide the following properties: - - **From** `required` : Email address of the sender - - **From Name** : Name of the sender - - **To** `required` : Recipient's email address - - **Subject** : Subject of the email +1. Open the query panel located at the bottom panel of the editor. +2. Click the `+Add` button on the left to create a new query. +3. Select `SMTP` from the global datasource. +4. Provide the following properties: + - **From** `required` : Email address of the sender + - **From Name** : Name of the sender + - **To** `required` : Recipient's email address + - **CC mail to** : Email address of the recipients that will receive a copy of the email, and their email addresses will be visible to other recipients. + - **BCC mail to** : Email address of the recipients that will receive a copy of the email but the email addressed will be hidden to other recipients. + - **Subject** : Subject of the email. + - **Body** : You can enter the body text of the email in either raw text or html format, in their respective fields. + - **Attachments** : You can add attachments to an SMTP query by referencing the file from the File Picker component in the attachments field. +For instance, you can set the `Attachments` field value to `{{ components.filepicker1.file }}` or pass an array of `{{ name: 'filename.jpg', dataURL: '......' }}` objects to include attachments. -smtp query1 +
+smtp connect - - **Body** : You can enter the body text either in the form of `raw text` or `html` in their respective fields. - - **Attachments** : Attachments can be added to a SMTP query by referencing the file from the `File Picker` component in the attachments field. - - For example, you can set the `Attachments` field value to `{{ components.filepicker1.file }}` or you can pass an array of `{{ name: 'filename.jpg', dataURL: '......' }}` object to accomplish this. - - -smtp query2 +
\ No newline at end of file diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md index 3d944f62c9..42699a3b53 100644 --- a/docs/docs/getting-started.md +++ b/docs/docs/getting-started.md @@ -143,7 +143,7 @@ Learn more about the **[ToolJet Database here](/docs/tooljet-database)**
:::info -ToolJet application's User interface is constructed using Components like Tables, Forms, Charts, or Buttons etc. Check **Components Catalog** to learn more. +ToolJet application's User interface is constructed using Components like Tables, Forms, Charts, or Buttons etc. Check **[Components Catalog](/docs/widgets/overview)** to learn more. ::: ### Build queries and bind data to UI @@ -156,7 +156,7 @@ ToolJet application's User interface is constructed using Components like Tables :::info - ToolJet can connect to several databases, APIs and external services to fetch and modify data. Check **Datasource Catalog** to learn more. + ToolJet can connect to several databases, APIs and external services to fetch and modify data. Check **[Datasource Catalog](/docs/data-sources/overview)** to learn more. ::: 2. Choose a **Table** from the dropdown, Select the **List rows** option from the **Operation** dropdown, You can leave other query parameters. Scroll down and enable **Run this query on application load** - this will trigger the query when the app is loaded. @@ -199,8 +199,8 @@ ToolJet application's User interface is constructed using Components like Tables :::info -- You can manipulate the data returned by the queries using **Transformations** -- You can also **Run JS query** or **Python query** to perform custom behavior inside ToolJet +- You can manipulate the data returned by the queries using **[Transformations](/docs/tutorial/transformations)** +- You can also **[Run JavaScript code](/docs/data-sources/run-js)** or **[Run Python code](/docs/data-sources/run-py)** to perform custom behavior inside ToolJet ::: ### Preview, Release and Share app @@ -210,7 +210,7 @@ ToolJet application's User interface is constructed using Components like Tables 3. **Share** option allows you to share the **released version** of the application with other users or you can also make the app **public** and anyone with the URL will be able to use the app. :::tip -You can control how much access to users have to your ToolJet apps and resources using **Org Management**. +You can control how much access to users have to your ToolJet apps and resources using **[Org Management](/docs/tutorial/manage-users-groups)**. ::: ## What Can I Do With ToolJet diff --git a/docs/docs/how-to/intentionally-fail-js-query.md b/docs/docs/how-to/intentionally-fail-js-query.md new file mode 100644 index 0000000000..bc7750ec4c --- /dev/null +++ b/docs/docs/how-to/intentionally-fail-js-query.md @@ -0,0 +1,23 @@ +--- +id: intentionally-fail-js-query +title: Intentionally fail a RunJS query +--- + +In this how-to guide, we will create a RunJS query that will throw an error. + +- Create a RunJS query and paste the code below. We will use the constructor `ReferenceError` since it is used to create a range error instance. + ```js + throw new ReferenceError('This is a reference error.'); + ``` + +- Now, add a event handler to show an alert when the query fails. **Save** the query and **Run** it. + +
+ + Intentionally fail a RunJS query + +
+ +:::info +Most common use-case for intentionally failing a query is **debugging**. +::: \ No newline at end of file diff --git a/docs/docs/how-to/use-s3-presigned-url-to-upload-docs.md b/docs/docs/how-to/use-s3-presigned-url-to-upload-docs.md new file mode 100644 index 0000000000..61dd6448d3 --- /dev/null +++ b/docs/docs/how-to/use-s3-presigned-url-to-upload-docs.md @@ -0,0 +1,173 @@ +--- +id: use-s3-signed-url-to-upload-docs +title: Use S3 signed URL to upload documents +--- + +# Use S3 signed URL to upload documents + +In this how-to guide, you'll learn to upload documents to S3 buckets using the **S3 signed URL** from a ToolJet application. + +For this guide, We are going to use one of the existing templates on ToolJet: **S3 File explorer** + +:::info using Templates +On ToolJet Dashboard, Click on the down arrow on the right of the **New App** button, from the dropdown choose the **Choose from template** option. +::: + +
+ +Use S3 pre-signed URL to upload documents: Choose template + +
+ +- Once you've created a new app using the template, you'll be prompted to create a **new version** of the existing version. After creating a new version, you'll be able to make changes in the app. + +
+ + Use S3 pre-signed URL to upload documents: new version + +
+ +- Go to the **datasource manager** on the left-sidebar, you'll find that the **AWS S3 datasource** is already added. All you need to do is update the datasource **credentials**. + + :::tip + Check the [AWS S3 datasource reference](/docs/data-sources/s3) to learn more about connnection and choosing your preferred authentication method. + ::: + +
+ + Use S3 pre-signed URL to upload documents: add datasource + +
+ +- Once the datasource is connected successfully, go to the query manager and **Run** the **getBuckets** query. The operation selected in the getBuckets query is **List Buckets** which will fetch an array of all the buckets. + +
+ + Use S3 pre-signed URL to upload documents: getBuckets query + +
+ +- Running the **getBuckets** query will load all the buckets in the dropdown in the app. + +
+ + Use S3 pre-signed URL to upload documents: loading buckets + +
+ +- Select a **bucket** from the dropdown and click on the **Fetch files** button to list all the files from the selected bucket on the table. The **Fetch files** button has the event handler added that triggers the **s32** query, the **s32** query uses the **List objects in a bucket** operation, and the bucket field in the query gets the value dynamically from the dropdown. + +
+ + Use S3 pre-signed URL to upload documents: list objects in a bucket + +
+ +- Let's go to the **uploadToS3** query and update the field values: + - **Operation**: Signed URL for upload + - **Bucket**: `{{components.dropdown1.value}}` this will fetch the dynamic value from the dropdown + - **Key**: `{{components.filepicker1.file[0].name}}` this will get the file name from the filepickers exposed variables + - **Expires in:** This sets an expiration time of URL, by default its `3600` seconds (1 hour) + - **Content Type**: `{{components.filepicker1.file[0].type}}` this will get the file type from the filepickers exposed variables + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Create two **RunJS** queries: + - Create a **runjs1** query and copy-paste the code below. This query gets the **base64data** from the file picker and convert the file's `base64Data` to into `BLOB`, and returns the file object. + ```js + const base64String = components.filepicker1.file[0].base64Data + const decodedArray = new Uint8Array(atob(base64String).split('').map(c => c.charCodeAt(0))); + const file = new Blob([decodedArray], { type: components.filepicker1.file[0].type }); + const fileName = components.filepicker1.file[0].name; + const fileObj = new File([file], fileName); + + return fileObj + ``` + +
+ + Use S3 pre-signed URL to upload documents + +
+ + - Create another **runjs2** query and copy-paste the code below. This query gets the data(file object) returned by the first runjs query, the url returned by the **uploadToS3** query, and then makes PUT request. + ```js + const file = queries.runjs2.data + const url = queries.s31.data.url + + fetch(url, { + method: 'PUT', + body: file, + mode: 'cors', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json' + } + }) + .then(response => console.log('Upload successful!')) + .catch(error => console.error('Error uploading file:', error)); + ``` + :::warning Enable Cross Origin Resource Sharing(CORS) + - For the file to be uploaded successfully, you will need to add the CORS policies from the **Permissions** tab of your **Bucket** settings. Here's a sample CORS: + ```json + [ + { + "AllowedHeaders": [ + "*" + ], + "AllowedMethods": [ + "GET", + "PUT", + "POST" + ], + "AllowedOrigins": [ + "*" + ], + "ExposeHeaders": [] + } + ] + ``` + ::: + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Go to the **uploadToS3**, scroll down and add an event handler to the **uploadToS3** query. Select the **Query Success** event, **Run Query** as the action, and **runjs1** as the query to be triggered. **Save** the query. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Let's go to the **runjs1** query and add the event handler to run a query on query success event, similar to how we did in the previous step. In the event handler, choose **runjs2** query. **Save** the query. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Now, let's go the final query **copySignedURL** that is connected to the table's action button. This query copy's the generated **Signed URL for download** onto the **clipboard**. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Now that we have updated all the queries, and connected them through the event handlers. We can go ahead and pick a file from the file picker. Click on the file picker, select a file and then hit the **Upload file to S3** button. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Once the button is clicked, the **uploadToS3** will triggered along with the **runjs1** and **runjs2** queries in sequence since we added them in the event handlers. + +- You can go to the table and click on the **Copy signed URL** action button on the table, this will trigger the **copySignedURL** query and will copy the URL on the clipboard. You can go to another tab and paste the URL to open the file on the browser. + diff --git a/docs/docs/org-management/permissions.md b/docs/docs/org-management/permissions.md index 5e33f5cb2c..b13433e0ef 100644 --- a/docs/docs/org-management/permissions.md +++ b/docs/docs/org-management/permissions.md @@ -8,7 +8,7 @@ Permissions allow you to create and share resources to easily ensure what level Admins can invite **Users** to their workspaces and assign them to the **Groups** that have Permissions to access Apps, folders, or workspace variables. :::info -See **[Manage Users and Groups](/docs/tutorial/manage-users-groups)** to learn how to invite users to ToolJet. +See **[Manage Users and Groups](/docs/tutorial/manage-users-groups)** to know more about managing users and groups on your workspace. ::: ## Role-Based Access Control (RBAC) Glossary @@ -18,4 +18,4 @@ See **[Manage Users and Groups](/docs/tutorial/manage-users-groups)** to learn h - **All Users** - Contains all the users in your workspace. When **New Users** are invited they are added to this group by default. - **Admins** - Contains all Admins in your workspace. Everyone added to this group will Permission to access all the ToolJet resources. - **Apps, Folder, Workspace Variables -** Resources that Admins can set permissions on. -- **Permissions -** Create, Update and Delete. +- **Permissions -** Create, Update and Delete. \ No newline at end of file diff --git a/docs/docs/tutorial/manage-users-groups.md b/docs/docs/tutorial/manage-users-groups.md index 95991fd7fa..185d078f60 100644 --- a/docs/docs/tutorial/manage-users-groups.md +++ b/docs/docs/tutorial/manage-users-groups.md @@ -77,7 +77,7 @@ Similar to archiving a user's access, you can enable it again by clicking on **U ## Managing Groups -On ToolJet, Admins can create groups for users added in a workspace and grant them access to particular app(s) with specific permissions. To manage groups, just go to the **Workspace Settings** from the left-sidebar of the dashboard and click on the **Groups**. +On ToolJet, Admins and Super Admins can create groups for users added in a workspace and grant them access to particular app(s) with specific permissions. To manage groups, just go to the **Workspace Settings** from the left-sidebar of the dashboard and click on the **Groups**.
@@ -87,11 +87,16 @@ On ToolJet, Admins can create groups for users added in a workspace and grant th ### Group properties -Every group on ToolJet has three sections: +Every group on ToolJet has **four** sections: + +- [Apps](#apps) +- [Users](#users) +- [Permissions](#permissions) +- [Datasources](#datasources) #### Apps: -Admins can add or remove any number of apps for a group of users. To add an app to a group, select an app from the dropdown and click on `Add` button next to it. You can also set app permissions such as `View` or `Edit` for the group. You can set different permissions for different apps in a group. +Admins and Super Admins can add or remove any number of apps for a group of users. To add an app to a group, select an app from the dropdown and click on `Add` button next to it. You can also set app permissions such as `View` or `Edit` for the group. You can set different permissions for different apps in a group.
@@ -101,7 +106,7 @@ Admins can add or remove any number of apps for a group of users. To add an app #### Users: -Admins can add or remove any numbers of users in a group. Just select a user from the dropdown and click on `Add` button to add it to a group. To delete a user from a group, click on `Delete` button next to it. +Admins and Super Admins can add or remove any numbers of users in a group. Just select a user from the dropdown and click on `Add` button to add it to a group. To delete a user from a group, click on `Delete` button next to it.
@@ -111,16 +116,30 @@ Admins can add or remove any numbers of users in a group. Just select a user fro #### Permissions: -Admins can set granular permission like creating/deleting apps or creating folder for a group of users. +Admins and Super Admins can set granular permission for the users added in that particular group, such as: +- **Create** and **Delete** Apps +- **Create**, **Update**, and **Delete** Folders +- **Create**, **Update**, and **Delete** [Workspace Variables](/docs/tutorial/workspace-variables) +- **Create** and **Delete** [Global Datasources](/docs/widgets/overview)
-permissions +permissions + +
+ +#### Datasources: + +Only Admins and Super Admins can define what datasources can be **viewed** or **edited** by the users of that group. + +
+ +permissions
:::tip -All the activities performed by any Admin or any user in a workspace is logged in `Audit logs` - including any activity related with managing users and groups. +All the activities performed by any Admin, Super Admin or any user in a workspace is logged in [Audit logs](/docs/Enterprise/audit_logs) - including any activity related with managing users and groups. ::: ### Predefined Groups diff --git a/docs/docs/widgets/table.md b/docs/docs/widgets/table.md index 15b05f6fbd..793b161916 100644 --- a/docs/docs/widgets/table.md +++ b/docs/docs/widgets/table.md @@ -8,11 +8,64 @@ Tables can be used for both displaying and editing data. +## Table UI + +
+ +ToolJet - Widget Reference - Table + +
+ +### Search + +At the top-left corner of the table component, there is a search box that allows users to input keywords and search for rows within the table data. You can also **[show/hide the search box](/docs/widgets/table#show-search-box)** from the table from the table properties. + +### Add new row + +When users click on this button, a popup modal appears which enables them to insert new rows. The modal will have a single row initially, and the columns will have the same column type as those on the table. If the user inputs data into the row, it will be stored on the **[`newRows` variable](/docs/widgets/table#exposed-variables)** of the table. If the user selects the **Discard** button, the data in the variable will be cleared. However, if the user closes the popup without taking any action (neither Save nor Discard), the data will still be retained, and a green indicator will appear on the **Add new row** button. The table has an **[Add new rows event handler](/docs//widgets/table#add-new-rows)** that can be utilized to execute queries that store the data into the datasource whenever the **Save** button is clicked. + +:::info +At present, it is not possible to include columns of type Image when adding a new row to the table. +::: + +### Filters + +The table data can be filtered by clicking on this button. You have the option to choose from various filters, such as: + +- **contains** +- **does not contain** +- **matches** +- **does not match** +- **equals** +- **does not equal** +- **is empty** +- **is not empty** +- **greater than** +- **greater than or equal to** +- **less than** +- **less than or equal to** + +You have the option to **[hide the filter button](/docs/widgets/table#show-filter-button)** in the table properties. + +### Download + +The table data can be downloaded in various file formats, including: + +- **CSV** +- **Excel** +- **PDF** + +You have the option to **[hide the download button](/docs/widgets/table#show-download-button)** in the table properties. + +### Column selector button + +You can choose which columns to display or hide in the table by clicking on this button. You also have the option to **[hide the column selector button](/docs/widgets/table#show-column-selector-button)** in the table properties. + ## Table data
-ToolJet - Widget Reference - Table +ToolJet - Widget Reference - Table
@@ -197,28 +250,15 @@ If server-side search is enabled, `on search` event is fired after the content o ### Show download button -Show or hide download button at the Table footer. +The download button in the table header is visible by default. You can choose to hide it by disabling this option. You can dynamically set the value to {{true}} or {{false}} to show or hide the download button by clicking on the **Fx** button. -### Hide/Show columns +### Show column selector button -Table header has an option(Eye icon) to show/hide one or many columns on the table. +The column selector button on the table header is visible by default. You can choose to hide it by disabling this option. You can dynamically set the value to {{true}} or {{false}} to show or hide the column selector button by clicking on the **Fx** button. ### Show filter button -Show or hide filter button at the Table header. The following filters are available: -- **contains** -- **does not contain** -- **matches** -- **does not match** -- **equals** -- **does not equal to** -- **is empty** -- **is not empty** -- **greater than** -- **greater than or equal to** -- **less than** -- **less than or equal to** - +The filter button in the table header is visible by default. You can choose to hide it by disabling this option. You can dynamically set the value to {{true}} or {{false}} to show or hide the filter button by clicking on the **Fx** button. ### Show update buttons @@ -263,6 +303,7 @@ Loading state shows a loading skeleton for the table. This property can be used - **[Sort applied](#sort-applied)** - **[Cell value changed](#cell-value-changed)** - **[Filter changed](#filter-changed)** +- **[Add new rows](#add-new-rows)** ### Row hovered @@ -300,6 +341,10 @@ If any cell of the table is edited, the `cell value changed` event is triggered. This event is triggered when filter is added, removed, or updated from the filter section of the table. `filters` property of the table is updated to reflect the status of filters applied. The objects will have properties: `condition`, `value`, and `column`. +### Add new rows + +This event is triggered when the **Save** button is clicked from the **Add new row** modal on the table. + ## Exposed variables | variable | description | @@ -311,6 +356,7 @@ This event is triggered when filter is added, removed, or updated from the filte | dataUpdates | Just like changeSet but includes the data of the entire row | | selectedRow | The data of the row that was last clicked. `selectedRow` also changes when an action button is clicked | | searchText | The value of the search field if server-side pagination is enabled | +| newRows| The newRows variable stores an array of objects, each containing data for a row that was added to the table using the "Add new row" button. When the user clicks either the "Save" or "Discard" button in the modal, this data is cleared.| ## Styles diff --git a/docs/sidebars.js b/docs/sidebars.js index 7f2ae33f30..73f65d0182 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -303,8 +303,10 @@ const sidebars = { 'how-to/import-external-libraries-using-runpy', 'how-to/import-external-libraries-using-runjs', 'how-to/run-actions-from-runjs', + 'how-to/intentionally-fail-js-query', 'how-to/run-query-at-specified-intervals', 'how-to/access-users-location', + 'how-to/use-s3-signed-url-to-upload-docs', 'how-to/s3-custom-endpoints', 'how-to/oauth2-authorization', 'how-to/upload-files-aws', diff --git a/docs/static/img/datasource-reference/overview/changescope.png b/docs/static/img/datasource-reference/overview/changescope.png new file mode 100644 index 0000000000..95f0df6dbd Binary files /dev/null and b/docs/static/img/datasource-reference/overview/changescope.png differ diff --git a/docs/static/img/datasource-reference/overview/connection.png b/docs/static/img/datasource-reference/overview/connection.png new file mode 100644 index 0000000000..46f77abcdb Binary files /dev/null and b/docs/static/img/datasource-reference/overview/connection.png differ diff --git a/docs/static/img/datasource-reference/overview/create.png b/docs/static/img/datasource-reference/overview/create.png new file mode 100644 index 0000000000..093892dd6b Binary files /dev/null and b/docs/static/img/datasource-reference/overview/create.png differ diff --git a/docs/static/img/datasource-reference/overview/default.png b/docs/static/img/datasource-reference/overview/default.png new file mode 100644 index 0000000000..bb86b2c259 Binary files /dev/null and b/docs/static/img/datasource-reference/overview/default.png differ diff --git a/docs/static/img/datasource-reference/overview/edit.png b/docs/static/img/datasource-reference/overview/edit.png new file mode 100644 index 0000000000..ed5a82134a Binary files /dev/null and b/docs/static/img/datasource-reference/overview/edit.png differ diff --git a/docs/static/img/datasource-reference/overview/error.png b/docs/static/img/datasource-reference/overview/error.png new file mode 100644 index 0000000000..eafe42cef0 Binary files /dev/null and b/docs/static/img/datasource-reference/overview/error.png differ diff --git a/docs/static/img/datasource-reference/overview/global.png b/docs/static/img/datasource-reference/overview/global.png new file mode 100644 index 0000000000..746eacba93 Binary files /dev/null and b/docs/static/img/datasource-reference/overview/global.png differ diff --git a/docs/static/img/datasource-reference/overview/globalquery.png b/docs/static/img/datasource-reference/overview/globalquery.png new file mode 100644 index 0000000000..f2273650fc Binary files /dev/null and b/docs/static/img/datasource-reference/overview/globalquery.png differ diff --git a/docs/static/img/datasource-reference/overview/leftsidebar.png b/docs/static/img/datasource-reference/overview/leftsidebar.png new file mode 100644 index 0000000000..1d6f717eab Binary files /dev/null and b/docs/static/img/datasource-reference/overview/leftsidebar.png differ diff --git a/docs/static/img/datasource-reference/overview/popup.png b/docs/static/img/datasource-reference/overview/popup.png new file mode 100644 index 0000000000..a3d945b374 Binary files /dev/null and b/docs/static/img/datasource-reference/overview/popup.png differ diff --git a/docs/static/img/datasource-reference/overview/queryadd.png b/docs/static/img/datasource-reference/overview/queryadd.png new file mode 100644 index 0000000000..8caf9ff3dd Binary files /dev/null and b/docs/static/img/datasource-reference/overview/queryadd.png differ diff --git a/docs/static/img/datasource-reference/overview/switch.png b/docs/static/img/datasource-reference/overview/switch.png new file mode 100644 index 0000000000..817fe042d7 Binary files /dev/null and b/docs/static/img/datasource-reference/overview/switch.png differ diff --git a/docs/static/img/datasource-reference/overview/view.png b/docs/static/img/datasource-reference/overview/view.png new file mode 100644 index 0000000000..f811c45dd9 Binary files /dev/null and b/docs/static/img/datasource-reference/overview/view.png differ diff --git a/docs/static/img/datasource-reference/smtp/querysmtp.png b/docs/static/img/datasource-reference/smtp/querysmtp.png new file mode 100644 index 0000000000..75a7a8ca82 Binary files /dev/null and b/docs/static/img/datasource-reference/smtp/querysmtp.png differ diff --git a/docs/static/img/how-to/failjs/failjs.gif b/docs/static/img/how-to/failjs/failjs.gif new file mode 100644 index 0000000000..c042f682d4 Binary files /dev/null and b/docs/static/img/how-to/failjs/failjs.gif differ diff --git a/docs/static/img/how-to/uses3presignedurl/copysigned.png b/docs/static/img/how-to/uses3presignedurl/copysigned.png new file mode 100644 index 0000000000..ffdd0793c0 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/copysigned.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/dropdown.png b/docs/static/img/how-to/uses3presignedurl/dropdown.png new file mode 100644 index 0000000000..0c97c1c9a8 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/dropdown.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/eventhandlerrunjs2.png b/docs/static/img/how-to/uses3presignedurl/eventhandlerrunjs2.png new file mode 100644 index 0000000000..21ebd55481 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/eventhandlerrunjs2.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/eventhandlerupload.png b/docs/static/img/how-to/uses3presignedurl/eventhandlerupload.png new file mode 100644 index 0000000000..5670721423 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/eventhandlerupload.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/fetchfiles.png b/docs/static/img/how-to/uses3presignedurl/fetchfiles.png new file mode 100644 index 0000000000..21e548b7af Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/fetchfiles.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/getbuckets.png b/docs/static/img/how-to/uses3presignedurl/getbuckets.png new file mode 100644 index 0000000000..ba62cc882d Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/getbuckets.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/newversion.png b/docs/static/img/how-to/uses3presignedurl/newversion.png new file mode 100644 index 0000000000..aac030e909 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/newversion.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/runjs1.png b/docs/static/img/how-to/uses3presignedurl/runjs1.png new file mode 100644 index 0000000000..0313e21b49 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/runjs1.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/runjs2.png b/docs/static/img/how-to/uses3presignedurl/runjs2.png new file mode 100644 index 0000000000..17a13c6d06 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/runjs2.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/s3connect.png b/docs/static/img/how-to/uses3presignedurl/s3connect.png new file mode 100644 index 0000000000..7ade00fada Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/s3connect.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/template.png b/docs/static/img/how-to/uses3presignedurl/template.png new file mode 100644 index 0000000000..761705dee4 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/template.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/upload.png b/docs/static/img/how-to/uses3presignedurl/upload.png new file mode 100644 index 0000000000..93f1924c14 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/upload.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/uploadbutton.png b/docs/static/img/how-to/uses3presignedurl/uploadbutton.png new file mode 100644 index 0000000000..f580239948 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/uploadbutton.png differ diff --git a/docs/static/img/tutorial/manage-users-groups/dspermission.png b/docs/static/img/tutorial/manage-users-groups/dspermission.png new file mode 100644 index 0000000000..69e1c57a2d Binary files /dev/null and b/docs/static/img/tutorial/manage-users-groups/dspermission.png differ diff --git a/docs/static/img/tutorial/manage-users-groups/gdspermission.png b/docs/static/img/tutorial/manage-users-groups/gdspermission.png new file mode 100644 index 0000000000..35b8423a69 Binary files /dev/null and b/docs/static/img/tutorial/manage-users-groups/gdspermission.png differ diff --git a/docs/static/img/widgets/table/ui.png b/docs/static/img/widgets/table/ui.png new file mode 100644 index 0000000000..7a42690459 Binary files /dev/null and b/docs/static/img/widgets/table/ui.png differ diff --git a/docs/versioned_docs/version-1.x.x/how-to/intentionally-fail-js-query.md b/docs/versioned_docs/version-1.x.x/how-to/intentionally-fail-js-query.md new file mode 100644 index 0000000000..bc7750ec4c --- /dev/null +++ b/docs/versioned_docs/version-1.x.x/how-to/intentionally-fail-js-query.md @@ -0,0 +1,23 @@ +--- +id: intentionally-fail-js-query +title: Intentionally fail a RunJS query +--- + +In this how-to guide, we will create a RunJS query that will throw an error. + +- Create a RunJS query and paste the code below. We will use the constructor `ReferenceError` since it is used to create a range error instance. + ```js + throw new ReferenceError('This is a reference error.'); + ``` + +- Now, add a event handler to show an alert when the query fails. **Save** the query and **Run** it. + +
+ + Intentionally fail a RunJS query + +
+ +:::info +Most common use-case for intentionally failing a query is **debugging**. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-1.x.x/how-to/use-s3-presigned-url-to-upload-docs.md b/docs/versioned_docs/version-1.x.x/how-to/use-s3-presigned-url-to-upload-docs.md new file mode 100644 index 0000000000..61dd6448d3 --- /dev/null +++ b/docs/versioned_docs/version-1.x.x/how-to/use-s3-presigned-url-to-upload-docs.md @@ -0,0 +1,173 @@ +--- +id: use-s3-signed-url-to-upload-docs +title: Use S3 signed URL to upload documents +--- + +# Use S3 signed URL to upload documents + +In this how-to guide, you'll learn to upload documents to S3 buckets using the **S3 signed URL** from a ToolJet application. + +For this guide, We are going to use one of the existing templates on ToolJet: **S3 File explorer** + +:::info using Templates +On ToolJet Dashboard, Click on the down arrow on the right of the **New App** button, from the dropdown choose the **Choose from template** option. +::: + +
+ +Use S3 pre-signed URL to upload documents: Choose template + +
+ +- Once you've created a new app using the template, you'll be prompted to create a **new version** of the existing version. After creating a new version, you'll be able to make changes in the app. + +
+ + Use S3 pre-signed URL to upload documents: new version + +
+ +- Go to the **datasource manager** on the left-sidebar, you'll find that the **AWS S3 datasource** is already added. All you need to do is update the datasource **credentials**. + + :::tip + Check the [AWS S3 datasource reference](/docs/data-sources/s3) to learn more about connnection and choosing your preferred authentication method. + ::: + +
+ + Use S3 pre-signed URL to upload documents: add datasource + +
+ +- Once the datasource is connected successfully, go to the query manager and **Run** the **getBuckets** query. The operation selected in the getBuckets query is **List Buckets** which will fetch an array of all the buckets. + +
+ + Use S3 pre-signed URL to upload documents: getBuckets query + +
+ +- Running the **getBuckets** query will load all the buckets in the dropdown in the app. + +
+ + Use S3 pre-signed URL to upload documents: loading buckets + +
+ +- Select a **bucket** from the dropdown and click on the **Fetch files** button to list all the files from the selected bucket on the table. The **Fetch files** button has the event handler added that triggers the **s32** query, the **s32** query uses the **List objects in a bucket** operation, and the bucket field in the query gets the value dynamically from the dropdown. + +
+ + Use S3 pre-signed URL to upload documents: list objects in a bucket + +
+ +- Let's go to the **uploadToS3** query and update the field values: + - **Operation**: Signed URL for upload + - **Bucket**: `{{components.dropdown1.value}}` this will fetch the dynamic value from the dropdown + - **Key**: `{{components.filepicker1.file[0].name}}` this will get the file name from the filepickers exposed variables + - **Expires in:** This sets an expiration time of URL, by default its `3600` seconds (1 hour) + - **Content Type**: `{{components.filepicker1.file[0].type}}` this will get the file type from the filepickers exposed variables + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Create two **RunJS** queries: + - Create a **runjs1** query and copy-paste the code below. This query gets the **base64data** from the file picker and convert the file's `base64Data` to into `BLOB`, and returns the file object. + ```js + const base64String = components.filepicker1.file[0].base64Data + const decodedArray = new Uint8Array(atob(base64String).split('').map(c => c.charCodeAt(0))); + const file = new Blob([decodedArray], { type: components.filepicker1.file[0].type }); + const fileName = components.filepicker1.file[0].name; + const fileObj = new File([file], fileName); + + return fileObj + ``` + +
+ + Use S3 pre-signed URL to upload documents + +
+ + - Create another **runjs2** query and copy-paste the code below. This query gets the data(file object) returned by the first runjs query, the url returned by the **uploadToS3** query, and then makes PUT request. + ```js + const file = queries.runjs2.data + const url = queries.s31.data.url + + fetch(url, { + method: 'PUT', + body: file, + mode: 'cors', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json' + } + }) + .then(response => console.log('Upload successful!')) + .catch(error => console.error('Error uploading file:', error)); + ``` + :::warning Enable Cross Origin Resource Sharing(CORS) + - For the file to be uploaded successfully, you will need to add the CORS policies from the **Permissions** tab of your **Bucket** settings. Here's a sample CORS: + ```json + [ + { + "AllowedHeaders": [ + "*" + ], + "AllowedMethods": [ + "GET", + "PUT", + "POST" + ], + "AllowedOrigins": [ + "*" + ], + "ExposeHeaders": [] + } + ] + ``` + ::: + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Go to the **uploadToS3**, scroll down and add an event handler to the **uploadToS3** query. Select the **Query Success** event, **Run Query** as the action, and **runjs1** as the query to be triggered. **Save** the query. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Let's go to the **runjs1** query and add the event handler to run a query on query success event, similar to how we did in the previous step. In the event handler, choose **runjs2** query. **Save** the query. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Now, let's go the final query **copySignedURL** that is connected to the table's action button. This query copy's the generated **Signed URL for download** onto the **clipboard**. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Now that we have updated all the queries, and connected them through the event handlers. We can go ahead and pick a file from the file picker. Click on the file picker, select a file and then hit the **Upload file to S3** button. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Once the button is clicked, the **uploadToS3** will triggered along with the **runjs1** and **runjs2** queries in sequence since we added them in the event handlers. + +- You can go to the table and click on the **Copy signed URL** action button on the table, this will trigger the **copySignedURL** query and will copy the URL on the clipboard. You can go to another tab and paste the URL to open the file on the browser. + diff --git a/docs/versioned_docs/version-2.0.0/getting-started.md b/docs/versioned_docs/version-2.0.0/getting-started.md index 3d944f62c9..42699a3b53 100644 --- a/docs/versioned_docs/version-2.0.0/getting-started.md +++ b/docs/versioned_docs/version-2.0.0/getting-started.md @@ -143,7 +143,7 @@ Learn more about the **[ToolJet Database here](/docs/tooljet-database)**
:::info -ToolJet application's User interface is constructed using Components like Tables, Forms, Charts, or Buttons etc. Check **Components Catalog** to learn more. +ToolJet application's User interface is constructed using Components like Tables, Forms, Charts, or Buttons etc. Check **[Components Catalog](/docs/widgets/overview)** to learn more. ::: ### Build queries and bind data to UI @@ -156,7 +156,7 @@ ToolJet application's User interface is constructed using Components like Tables
:::info - ToolJet can connect to several databases, APIs and external services to fetch and modify data. Check **Datasource Catalog** to learn more. + ToolJet can connect to several databases, APIs and external services to fetch and modify data. Check **[Datasource Catalog](/docs/data-sources/overview)** to learn more. ::: 2. Choose a **Table** from the dropdown, Select the **List rows** option from the **Operation** dropdown, You can leave other query parameters. Scroll down and enable **Run this query on application load** - this will trigger the query when the app is loaded. @@ -199,8 +199,8 @@ ToolJet application's User interface is constructed using Components like Tables
:::info -- You can manipulate the data returned by the queries using **Transformations** -- You can also **Run JS query** or **Python query** to perform custom behavior inside ToolJet +- You can manipulate the data returned by the queries using **[Transformations](/docs/tutorial/transformations)** +- You can also **[Run JavaScript code](/docs/data-sources/run-js)** or **[Run Python code](/docs/data-sources/run-py)** to perform custom behavior inside ToolJet ::: ### Preview, Release and Share app @@ -210,7 +210,7 @@ ToolJet application's User interface is constructed using Components like Tables 3. **Share** option allows you to share the **released version** of the application with other users or you can also make the app **public** and anyone with the URL will be able to use the app. :::tip -You can control how much access to users have to your ToolJet apps and resources using **Org Management**. +You can control how much access to users have to your ToolJet apps and resources using **[Org Management](/docs/tutorial/manage-users-groups)**. ::: ## What Can I Do With ToolJet diff --git a/docs/versioned_docs/version-2.0.0/how-to/intentionally-fail-js-query.md b/docs/versioned_docs/version-2.0.0/how-to/intentionally-fail-js-query.md new file mode 100644 index 0000000000..bc7750ec4c --- /dev/null +++ b/docs/versioned_docs/version-2.0.0/how-to/intentionally-fail-js-query.md @@ -0,0 +1,23 @@ +--- +id: intentionally-fail-js-query +title: Intentionally fail a RunJS query +--- + +In this how-to guide, we will create a RunJS query that will throw an error. + +- Create a RunJS query and paste the code below. We will use the constructor `ReferenceError` since it is used to create a range error instance. + ```js + throw new ReferenceError('This is a reference error.'); + ``` + +- Now, add a event handler to show an alert when the query fails. **Save** the query and **Run** it. + +
+ + Intentionally fail a RunJS query + +
+ +:::info +Most common use-case for intentionally failing a query is **debugging**. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-2.0.0/how-to/use-s3-presigned-url-to-upload-docs.md b/docs/versioned_docs/version-2.0.0/how-to/use-s3-presigned-url-to-upload-docs.md new file mode 100644 index 0000000000..61dd6448d3 --- /dev/null +++ b/docs/versioned_docs/version-2.0.0/how-to/use-s3-presigned-url-to-upload-docs.md @@ -0,0 +1,173 @@ +--- +id: use-s3-signed-url-to-upload-docs +title: Use S3 signed URL to upload documents +--- + +# Use S3 signed URL to upload documents + +In this how-to guide, you'll learn to upload documents to S3 buckets using the **S3 signed URL** from a ToolJet application. + +For this guide, We are going to use one of the existing templates on ToolJet: **S3 File explorer** + +:::info using Templates +On ToolJet Dashboard, Click on the down arrow on the right of the **New App** button, from the dropdown choose the **Choose from template** option. +::: + +
+ +Use S3 pre-signed URL to upload documents: Choose template + +
+ +- Once you've created a new app using the template, you'll be prompted to create a **new version** of the existing version. After creating a new version, you'll be able to make changes in the app. + +
+ + Use S3 pre-signed URL to upload documents: new version + +
+ +- Go to the **datasource manager** on the left-sidebar, you'll find that the **AWS S3 datasource** is already added. All you need to do is update the datasource **credentials**. + + :::tip + Check the [AWS S3 datasource reference](/docs/data-sources/s3) to learn more about connnection and choosing your preferred authentication method. + ::: + +
+ + Use S3 pre-signed URL to upload documents: add datasource + +
+ +- Once the datasource is connected successfully, go to the query manager and **Run** the **getBuckets** query. The operation selected in the getBuckets query is **List Buckets** which will fetch an array of all the buckets. + +
+ + Use S3 pre-signed URL to upload documents: getBuckets query + +
+ +- Running the **getBuckets** query will load all the buckets in the dropdown in the app. + +
+ + Use S3 pre-signed URL to upload documents: loading buckets + +
+ +- Select a **bucket** from the dropdown and click on the **Fetch files** button to list all the files from the selected bucket on the table. The **Fetch files** button has the event handler added that triggers the **s32** query, the **s32** query uses the **List objects in a bucket** operation, and the bucket field in the query gets the value dynamically from the dropdown. + +
+ + Use S3 pre-signed URL to upload documents: list objects in a bucket + +
+ +- Let's go to the **uploadToS3** query and update the field values: + - **Operation**: Signed URL for upload + - **Bucket**: `{{components.dropdown1.value}}` this will fetch the dynamic value from the dropdown + - **Key**: `{{components.filepicker1.file[0].name}}` this will get the file name from the filepickers exposed variables + - **Expires in:** This sets an expiration time of URL, by default its `3600` seconds (1 hour) + - **Content Type**: `{{components.filepicker1.file[0].type}}` this will get the file type from the filepickers exposed variables + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Create two **RunJS** queries: + - Create a **runjs1** query and copy-paste the code below. This query gets the **base64data** from the file picker and convert the file's `base64Data` to into `BLOB`, and returns the file object. + ```js + const base64String = components.filepicker1.file[0].base64Data + const decodedArray = new Uint8Array(atob(base64String).split('').map(c => c.charCodeAt(0))); + const file = new Blob([decodedArray], { type: components.filepicker1.file[0].type }); + const fileName = components.filepicker1.file[0].name; + const fileObj = new File([file], fileName); + + return fileObj + ``` + +
+ + Use S3 pre-signed URL to upload documents + +
+ + - Create another **runjs2** query and copy-paste the code below. This query gets the data(file object) returned by the first runjs query, the url returned by the **uploadToS3** query, and then makes PUT request. + ```js + const file = queries.runjs2.data + const url = queries.s31.data.url + + fetch(url, { + method: 'PUT', + body: file, + mode: 'cors', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json' + } + }) + .then(response => console.log('Upload successful!')) + .catch(error => console.error('Error uploading file:', error)); + ``` + :::warning Enable Cross Origin Resource Sharing(CORS) + - For the file to be uploaded successfully, you will need to add the CORS policies from the **Permissions** tab of your **Bucket** settings. Here's a sample CORS: + ```json + [ + { + "AllowedHeaders": [ + "*" + ], + "AllowedMethods": [ + "GET", + "PUT", + "POST" + ], + "AllowedOrigins": [ + "*" + ], + "ExposeHeaders": [] + } + ] + ``` + ::: + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Go to the **uploadToS3**, scroll down and add an event handler to the **uploadToS3** query. Select the **Query Success** event, **Run Query** as the action, and **runjs1** as the query to be triggered. **Save** the query. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Let's go to the **runjs1** query and add the event handler to run a query on query success event, similar to how we did in the previous step. In the event handler, choose **runjs2** query. **Save** the query. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Now, let's go the final query **copySignedURL** that is connected to the table's action button. This query copy's the generated **Signed URL for download** onto the **clipboard**. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Now that we have updated all the queries, and connected them through the event handlers. We can go ahead and pick a file from the file picker. Click on the file picker, select a file and then hit the **Upload file to S3** button. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Once the button is clicked, the **uploadToS3** will triggered along with the **runjs1** and **runjs2** queries in sequence since we added them in the event handlers. + +- You can go to the table and click on the **Copy signed URL** action button on the table, this will trigger the **copySignedURL** query and will copy the URL on the clipboard. You can go to another tab and paste the URL to open the file on the browser. + diff --git a/docs/versioned_docs/version-2.1.0/getting-started.md b/docs/versioned_docs/version-2.1.0/getting-started.md index 3d944f62c9..42699a3b53 100644 --- a/docs/versioned_docs/version-2.1.0/getting-started.md +++ b/docs/versioned_docs/version-2.1.0/getting-started.md @@ -143,7 +143,7 @@ Learn more about the **[ToolJet Database here](/docs/tooljet-database)** :::info -ToolJet application's User interface is constructed using Components like Tables, Forms, Charts, or Buttons etc. Check **Components Catalog** to learn more. +ToolJet application's User interface is constructed using Components like Tables, Forms, Charts, or Buttons etc. Check **[Components Catalog](/docs/widgets/overview)** to learn more. ::: ### Build queries and bind data to UI @@ -156,7 +156,7 @@ ToolJet application's User interface is constructed using Components like Tables :::info - ToolJet can connect to several databases, APIs and external services to fetch and modify data. Check **Datasource Catalog** to learn more. + ToolJet can connect to several databases, APIs and external services to fetch and modify data. Check **[Datasource Catalog](/docs/data-sources/overview)** to learn more. ::: 2. Choose a **Table** from the dropdown, Select the **List rows** option from the **Operation** dropdown, You can leave other query parameters. Scroll down and enable **Run this query on application load** - this will trigger the query when the app is loaded. @@ -199,8 +199,8 @@ ToolJet application's User interface is constructed using Components like Tables :::info -- You can manipulate the data returned by the queries using **Transformations** -- You can also **Run JS query** or **Python query** to perform custom behavior inside ToolJet +- You can manipulate the data returned by the queries using **[Transformations](/docs/tutorial/transformations)** +- You can also **[Run JavaScript code](/docs/data-sources/run-js)** or **[Run Python code](/docs/data-sources/run-py)** to perform custom behavior inside ToolJet ::: ### Preview, Release and Share app @@ -210,7 +210,7 @@ ToolJet application's User interface is constructed using Components like Tables 3. **Share** option allows you to share the **released version** of the application with other users or you can also make the app **public** and anyone with the URL will be able to use the app. :::tip -You can control how much access to users have to your ToolJet apps and resources using **Org Management**. +You can control how much access to users have to your ToolJet apps and resources using **[Org Management](/docs/tutorial/manage-users-groups)**. ::: ## What Can I Do With ToolJet diff --git a/docs/versioned_docs/version-2.1.0/how-to/intentionally-fail-js-query.md b/docs/versioned_docs/version-2.1.0/how-to/intentionally-fail-js-query.md new file mode 100644 index 0000000000..bc7750ec4c --- /dev/null +++ b/docs/versioned_docs/version-2.1.0/how-to/intentionally-fail-js-query.md @@ -0,0 +1,23 @@ +--- +id: intentionally-fail-js-query +title: Intentionally fail a RunJS query +--- + +In this how-to guide, we will create a RunJS query that will throw an error. + +- Create a RunJS query and paste the code below. We will use the constructor `ReferenceError` since it is used to create a range error instance. + ```js + throw new ReferenceError('This is a reference error.'); + ``` + +- Now, add a event handler to show an alert when the query fails. **Save** the query and **Run** it. + +
+ + Intentionally fail a RunJS query + +
+ +:::info +Most common use-case for intentionally failing a query is **debugging**. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-2.1.0/how-to/use-s3-presigned-url-to-upload-docs.md b/docs/versioned_docs/version-2.1.0/how-to/use-s3-presigned-url-to-upload-docs.md new file mode 100644 index 0000000000..61dd6448d3 --- /dev/null +++ b/docs/versioned_docs/version-2.1.0/how-to/use-s3-presigned-url-to-upload-docs.md @@ -0,0 +1,173 @@ +--- +id: use-s3-signed-url-to-upload-docs +title: Use S3 signed URL to upload documents +--- + +# Use S3 signed URL to upload documents + +In this how-to guide, you'll learn to upload documents to S3 buckets using the **S3 signed URL** from a ToolJet application. + +For this guide, We are going to use one of the existing templates on ToolJet: **S3 File explorer** + +:::info using Templates +On ToolJet Dashboard, Click on the down arrow on the right of the **New App** button, from the dropdown choose the **Choose from template** option. +::: + +
+ +Use S3 pre-signed URL to upload documents: Choose template + +
+ +- Once you've created a new app using the template, you'll be prompted to create a **new version** of the existing version. After creating a new version, you'll be able to make changes in the app. + +
+ + Use S3 pre-signed URL to upload documents: new version + +
+ +- Go to the **datasource manager** on the left-sidebar, you'll find that the **AWS S3 datasource** is already added. All you need to do is update the datasource **credentials**. + + :::tip + Check the [AWS S3 datasource reference](/docs/data-sources/s3) to learn more about connnection and choosing your preferred authentication method. + ::: + +
+ + Use S3 pre-signed URL to upload documents: add datasource + +
+ +- Once the datasource is connected successfully, go to the query manager and **Run** the **getBuckets** query. The operation selected in the getBuckets query is **List Buckets** which will fetch an array of all the buckets. + +
+ + Use S3 pre-signed URL to upload documents: getBuckets query + +
+ +- Running the **getBuckets** query will load all the buckets in the dropdown in the app. + +
+ + Use S3 pre-signed URL to upload documents: loading buckets + +
+ +- Select a **bucket** from the dropdown and click on the **Fetch files** button to list all the files from the selected bucket on the table. The **Fetch files** button has the event handler added that triggers the **s32** query, the **s32** query uses the **List objects in a bucket** operation, and the bucket field in the query gets the value dynamically from the dropdown. + +
+ + Use S3 pre-signed URL to upload documents: list objects in a bucket + +
+ +- Let's go to the **uploadToS3** query and update the field values: + - **Operation**: Signed URL for upload + - **Bucket**: `{{components.dropdown1.value}}` this will fetch the dynamic value from the dropdown + - **Key**: `{{components.filepicker1.file[0].name}}` this will get the file name from the filepickers exposed variables + - **Expires in:** This sets an expiration time of URL, by default its `3600` seconds (1 hour) + - **Content Type**: `{{components.filepicker1.file[0].type}}` this will get the file type from the filepickers exposed variables + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Create two **RunJS** queries: + - Create a **runjs1** query and copy-paste the code below. This query gets the **base64data** from the file picker and convert the file's `base64Data` to into `BLOB`, and returns the file object. + ```js + const base64String = components.filepicker1.file[0].base64Data + const decodedArray = new Uint8Array(atob(base64String).split('').map(c => c.charCodeAt(0))); + const file = new Blob([decodedArray], { type: components.filepicker1.file[0].type }); + const fileName = components.filepicker1.file[0].name; + const fileObj = new File([file], fileName); + + return fileObj + ``` + +
+ + Use S3 pre-signed URL to upload documents + +
+ + - Create another **runjs2** query and copy-paste the code below. This query gets the data(file object) returned by the first runjs query, the url returned by the **uploadToS3** query, and then makes PUT request. + ```js + const file = queries.runjs2.data + const url = queries.s31.data.url + + fetch(url, { + method: 'PUT', + body: file, + mode: 'cors', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json' + } + }) + .then(response => console.log('Upload successful!')) + .catch(error => console.error('Error uploading file:', error)); + ``` + :::warning Enable Cross Origin Resource Sharing(CORS) + - For the file to be uploaded successfully, you will need to add the CORS policies from the **Permissions** tab of your **Bucket** settings. Here's a sample CORS: + ```json + [ + { + "AllowedHeaders": [ + "*" + ], + "AllowedMethods": [ + "GET", + "PUT", + "POST" + ], + "AllowedOrigins": [ + "*" + ], + "ExposeHeaders": [] + } + ] + ``` + ::: + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Go to the **uploadToS3**, scroll down and add an event handler to the **uploadToS3** query. Select the **Query Success** event, **Run Query** as the action, and **runjs1** as the query to be triggered. **Save** the query. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Let's go to the **runjs1** query and add the event handler to run a query on query success event, similar to how we did in the previous step. In the event handler, choose **runjs2** query. **Save** the query. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Now, let's go the final query **copySignedURL** that is connected to the table's action button. This query copy's the generated **Signed URL for download** onto the **clipboard**. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Now that we have updated all the queries, and connected them through the event handlers. We can go ahead and pick a file from the file picker. Click on the file picker, select a file and then hit the **Upload file to S3** button. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Once the button is clicked, the **uploadToS3** will triggered along with the **runjs1** and **runjs2** queries in sequence since we added them in the event handlers. + +- You can go to the table and click on the **Copy signed URL** action button on the table, this will trigger the **copySignedURL** query and will copy the URL on the clipboard. You can go to another tab and paste the URL to open the file on the browser. + diff --git a/docs/versioned_docs/version-2.2.0/getting-started.md b/docs/versioned_docs/version-2.2.0/getting-started.md index 3d944f62c9..42699a3b53 100644 --- a/docs/versioned_docs/version-2.2.0/getting-started.md +++ b/docs/versioned_docs/version-2.2.0/getting-started.md @@ -143,7 +143,7 @@ Learn more about the **[ToolJet Database here](/docs/tooljet-database)** :::info -ToolJet application's User interface is constructed using Components like Tables, Forms, Charts, or Buttons etc. Check **Components Catalog** to learn more. +ToolJet application's User interface is constructed using Components like Tables, Forms, Charts, or Buttons etc. Check **[Components Catalog](/docs/widgets/overview)** to learn more. ::: ### Build queries and bind data to UI @@ -156,7 +156,7 @@ ToolJet application's User interface is constructed using Components like Tables :::info - ToolJet can connect to several databases, APIs and external services to fetch and modify data. Check **Datasource Catalog** to learn more. + ToolJet can connect to several databases, APIs and external services to fetch and modify data. Check **[Datasource Catalog](/docs/data-sources/overview)** to learn more. ::: 2. Choose a **Table** from the dropdown, Select the **List rows** option from the **Operation** dropdown, You can leave other query parameters. Scroll down and enable **Run this query on application load** - this will trigger the query when the app is loaded. @@ -199,8 +199,8 @@ ToolJet application's User interface is constructed using Components like Tables :::info -- You can manipulate the data returned by the queries using **Transformations** -- You can also **Run JS query** or **Python query** to perform custom behavior inside ToolJet +- You can manipulate the data returned by the queries using **[Transformations](/docs/tutorial/transformations)** +- You can also **[Run JavaScript code](/docs/data-sources/run-js)** or **[Run Python code](/docs/data-sources/run-py)** to perform custom behavior inside ToolJet ::: ### Preview, Release and Share app @@ -210,7 +210,7 @@ ToolJet application's User interface is constructed using Components like Tables 3. **Share** option allows you to share the **released version** of the application with other users or you can also make the app **public** and anyone with the URL will be able to use the app. :::tip -You can control how much access to users have to your ToolJet apps and resources using **Org Management**. +You can control how much access to users have to your ToolJet apps and resources using **[Org Management](/docs/tutorial/manage-users-groups)**. ::: ## What Can I Do With ToolJet diff --git a/docs/versioned_docs/version-2.2.0/how-to/intentionally-fail-js-query.md b/docs/versioned_docs/version-2.2.0/how-to/intentionally-fail-js-query.md new file mode 100644 index 0000000000..bc7750ec4c --- /dev/null +++ b/docs/versioned_docs/version-2.2.0/how-to/intentionally-fail-js-query.md @@ -0,0 +1,23 @@ +--- +id: intentionally-fail-js-query +title: Intentionally fail a RunJS query +--- + +In this how-to guide, we will create a RunJS query that will throw an error. + +- Create a RunJS query and paste the code below. We will use the constructor `ReferenceError` since it is used to create a range error instance. + ```js + throw new ReferenceError('This is a reference error.'); + ``` + +- Now, add a event handler to show an alert when the query fails. **Save** the query and **Run** it. + +
+ + Intentionally fail a RunJS query + +
+ +:::info +Most common use-case for intentionally failing a query is **debugging**. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-2.2.0/how-to/use-s3-presigned-url-to-upload-docs.md b/docs/versioned_docs/version-2.2.0/how-to/use-s3-presigned-url-to-upload-docs.md new file mode 100644 index 0000000000..61dd6448d3 --- /dev/null +++ b/docs/versioned_docs/version-2.2.0/how-to/use-s3-presigned-url-to-upload-docs.md @@ -0,0 +1,173 @@ +--- +id: use-s3-signed-url-to-upload-docs +title: Use S3 signed URL to upload documents +--- + +# Use S3 signed URL to upload documents + +In this how-to guide, you'll learn to upload documents to S3 buckets using the **S3 signed URL** from a ToolJet application. + +For this guide, We are going to use one of the existing templates on ToolJet: **S3 File explorer** + +:::info using Templates +On ToolJet Dashboard, Click on the down arrow on the right of the **New App** button, from the dropdown choose the **Choose from template** option. +::: + +
+ +Use S3 pre-signed URL to upload documents: Choose template + +
+ +- Once you've created a new app using the template, you'll be prompted to create a **new version** of the existing version. After creating a new version, you'll be able to make changes in the app. + +
+ + Use S3 pre-signed URL to upload documents: new version + +
+ +- Go to the **datasource manager** on the left-sidebar, you'll find that the **AWS S3 datasource** is already added. All you need to do is update the datasource **credentials**. + + :::tip + Check the [AWS S3 datasource reference](/docs/data-sources/s3) to learn more about connnection and choosing your preferred authentication method. + ::: + +
+ + Use S3 pre-signed URL to upload documents: add datasource + +
+ +- Once the datasource is connected successfully, go to the query manager and **Run** the **getBuckets** query. The operation selected in the getBuckets query is **List Buckets** which will fetch an array of all the buckets. + +
+ + Use S3 pre-signed URL to upload documents: getBuckets query + +
+ +- Running the **getBuckets** query will load all the buckets in the dropdown in the app. + +
+ + Use S3 pre-signed URL to upload documents: loading buckets + +
+ +- Select a **bucket** from the dropdown and click on the **Fetch files** button to list all the files from the selected bucket on the table. The **Fetch files** button has the event handler added that triggers the **s32** query, the **s32** query uses the **List objects in a bucket** operation, and the bucket field in the query gets the value dynamically from the dropdown. + +
+ + Use S3 pre-signed URL to upload documents: list objects in a bucket + +
+ +- Let's go to the **uploadToS3** query and update the field values: + - **Operation**: Signed URL for upload + - **Bucket**: `{{components.dropdown1.value}}` this will fetch the dynamic value from the dropdown + - **Key**: `{{components.filepicker1.file[0].name}}` this will get the file name from the filepickers exposed variables + - **Expires in:** This sets an expiration time of URL, by default its `3600` seconds (1 hour) + - **Content Type**: `{{components.filepicker1.file[0].type}}` this will get the file type from the filepickers exposed variables + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Create two **RunJS** queries: + - Create a **runjs1** query and copy-paste the code below. This query gets the **base64data** from the file picker and convert the file's `base64Data` to into `BLOB`, and returns the file object. + ```js + const base64String = components.filepicker1.file[0].base64Data + const decodedArray = new Uint8Array(atob(base64String).split('').map(c => c.charCodeAt(0))); + const file = new Blob([decodedArray], { type: components.filepicker1.file[0].type }); + const fileName = components.filepicker1.file[0].name; + const fileObj = new File([file], fileName); + + return fileObj + ``` + +
+ + Use S3 pre-signed URL to upload documents + +
+ + - Create another **runjs2** query and copy-paste the code below. This query gets the data(file object) returned by the first runjs query, the url returned by the **uploadToS3** query, and then makes PUT request. + ```js + const file = queries.runjs2.data + const url = queries.s31.data.url + + fetch(url, { + method: 'PUT', + body: file, + mode: 'cors', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json' + } + }) + .then(response => console.log('Upload successful!')) + .catch(error => console.error('Error uploading file:', error)); + ``` + :::warning Enable Cross Origin Resource Sharing(CORS) + - For the file to be uploaded successfully, you will need to add the CORS policies from the **Permissions** tab of your **Bucket** settings. Here's a sample CORS: + ```json + [ + { + "AllowedHeaders": [ + "*" + ], + "AllowedMethods": [ + "GET", + "PUT", + "POST" + ], + "AllowedOrigins": [ + "*" + ], + "ExposeHeaders": [] + } + ] + ``` + ::: + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Go to the **uploadToS3**, scroll down and add an event handler to the **uploadToS3** query. Select the **Query Success** event, **Run Query** as the action, and **runjs1** as the query to be triggered. **Save** the query. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Let's go to the **runjs1** query and add the event handler to run a query on query success event, similar to how we did in the previous step. In the event handler, choose **runjs2** query. **Save** the query. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Now, let's go the final query **copySignedURL** that is connected to the table's action button. This query copy's the generated **Signed URL for download** onto the **clipboard**. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Now that we have updated all the queries, and connected them through the event handlers. We can go ahead and pick a file from the file picker. Click on the file picker, select a file and then hit the **Upload file to S3** button. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Once the button is clicked, the **uploadToS3** will triggered along with the **runjs1** and **runjs2** queries in sequence since we added them in the event handlers. + +- You can go to the table and click on the **Copy signed URL** action button on the table, this will trigger the **copySignedURL** query and will copy the URL on the clipboard. You can go to another tab and paste the URL to open the file on the browser. + diff --git a/docs/versioned_docs/version-2.3.0/Enterprise/superadmin.md b/docs/versioned_docs/version-2.3.0/Enterprise/superadmin.md index 10ebe5551b..43332fd236 100644 --- a/docs/versioned_docs/version-2.3.0/Enterprise/superadmin.md +++ b/docs/versioned_docs/version-2.3.0/Enterprise/superadmin.md @@ -17,13 +17,14 @@ The user details entered while setting up ToolJet will have Super Admin privileg | Manage Groups in their workspace (Create Group/Add or Delete Users from groups/ Modify Group Permissions) | ✅ | ✅ | | Manage SSO in their workspace | ✅ | ✅ | | Manage Workspace Variables in their workspace | ✅ | ✅ | +| [Manage Global datasources for the user group in their workspace](/docs/data-sources/overview#permissions) | ✅ | ✅ | | [Access any user's personal workspace (create, edit or delete apps)](#access-any-workspace) | ❌ | ✅ | | [Archive Admin or any user of any workspace](#archiveunarchive-users) | ❌ | ✅ | | [Access any user's ToolJet database (create, edit or delete database)](#access-tooljet-db-in-any-workspace) | ❌ | ✅ | | [Manage any workspace's setting (Groups/SSO/Workspace Variables)](#manage-workspace-setting-groupsssoworkspace-variables) | ❌ | ✅ | | [Manage all users from all the workspaces in the instance](#checking-all-the-users-in-the-instance) | ❌ | ✅ | | [Make any user Super Admin](#make-the-user-super-admin) | ❌ | ✅ | -| [Restrict personal workspace of invited users](#allow-users-to-create-personal-workspace) | ❌ | ✅ | +| [Restrict creation of personal workspace of users](#restrict-creation-of-personal-workspace-of-users) | ❌ | ✅ |
@@ -117,11 +118,11 @@ The user will become Super Admin and the Type column will update from **`workspa
-### Allow users to create personal workspace +### Restrict creation of personal workspace of users When a user joins a workspace, they are provided with their own personal workspace and option to create new workspaces. -Super Admins can control this behavior from the Manage Instance Settings page, they can **toggle off** the option to **Allow personal workspace**. Now whenever a user joins a workspace they won't be provided a personal workspace nor they will be able to create a new workspace in the instance. +Super Admins can **control** this behavior from the Manage Instance Settings page, they can **toggle off** the option to **Allow personal workspace**. Now whenever a user joins a workspace they won't be provided a personal workspace nor they will be able to create a new workspace in the instance.
diff --git a/docs/versioned_docs/version-2.3.0/data-sources/overview.md b/docs/versioned_docs/version-2.3.0/data-sources/overview.md index ec7527f8af..7e72107e61 100644 --- a/docs/versioned_docs/version-2.3.0/data-sources/overview.md +++ b/docs/versioned_docs/version-2.3.0/data-sources/overview.md @@ -3,9 +3,13 @@ id: overview title: Overview --- -# Datasources : Overview +# Global Datasources : Overview -Datasources pull in and push data to any source including databases, external APIs, or services. +Global datasources pull in and push data to any source including databases, external APIs, or services. Once a global datasource is connected to a workspace, the connection can be shared with any app of that workspace. + +:::caution +Global datasources are available only on **ToolJet version 2.3.0 and above**. +:::
@@ -13,30 +17,115 @@ Datasources pull in and push data to any source including databases, external AP
-## Connecting datasources +## Connecting global datasources -1. After logging in to ToolJet, create a new app from the dashboard +1. From the ToolJet dashboard, go to the **global datasources page** from the left sidebar. +
-2. There are two ways for connecting a datasource. You can connect from: - 1. **Left-sidebar**: On the left sidebar, click on the `datasource` icon and then click on the `+ add datasource` button + Datasources: Overview -
+
- Datasources: Overview +2. Click on the **Add new datasource** button, a modal will pop-up with all the available global datasources. +
-
+ Datasources: Overview - 2. **Query Panel**: Go to the query panel at the bottom, click on the `+Add` button and then click `Add datasource` button +
-
+3. Select the datasource, enter the **Credentials** and **Save** the datasource. +
- Datasources: Overview + Datasources: Overview -
+
-3. Follow the steps in the **[Datasource Library](/docs/data-sources/airtable)** specific to the desired datasource +4. Now, go back to the dashboard, create a new app, and the datasource will be available on the query panel under **Global Datasources**. Added datasources will be available on any of the **existing** or the **new applications**. +
+ + Datasources: Overview + +
+ +5. You can now create queries of the connected global datasource. From the queries, you'll be able to switch to **different connections** of the same datasource if there are more than one connections created. +
+ + Datasources: Overview + +
+ +## Changing scope of datasources of an app created on older versions of ToolJet + +On ToolJet versions below 2.3.0, the datasource connection was made from within the individual apps. To make it backward compatible, we added an option to change the scope of the datasources and make it global datasource. + +1. If you open an app created on previos versions of ToolJet, you'll find the datasource manager on the left sidebar of the App Builder. +
+ + Datasources: Overview + +
+ +2. Click on the kebab menu next to the connected datasource, select the **change scope** option. +
+ + Datasources: Overview + +
+ +3. Once you change the scope of the datasource and make it global, you'll see that the **datasource manager** is removed from the left sidebar and now you'll find the datasource on the **query panel** under Global Datasources. You can now configure the datasource fromt the Global Datasource page on the **dashboard**. +
+ + Datasources: Overview + +
+ + +## Default datasources + +By default, 4 datasources will be available on every app on ToolJet: +- **[ToolJet Database](/docs/tooljet-database/)** +- **[RestAPI](/docs/data-sources/restapi/)** +- **[Run JavaScript Query](/docs/data-sources/run-js/)** +- **[Run Python Query](/docs/data-sources/run-py/)** + +
+ + Datasources: Overview + +
+ +## Permissions + +Only **Admins** and **[Super Admins](/docs/Enterprise/superadmin)** of the workspace can change the **[Permissions](/docs/tutorial/manage-users-groups#group-properties)** for Global Datasource. + +From **Workspace Settings** -> **Groups Settings**, Admins and Super Admins can set the permission for a user group to: + +- **Create** and **Delete** datasources onto that workspace. If only **Create** permission is set then the users of the group will only be able to add new datasources on the workspace, and if only **Delete** permission is set then the users of the group will only be able to delete the connected datasources on the workspace. +
+ + Datasources: Overview + +
+ + - If any of the permission(Create or Delete) is not enabled for a user group then the users of the group will get an error toast when they try to Add or Delete the global datasource. +
+ + Datasources: Overview + +
+ +- **View** or **Edit** allowed global datasources from the **Datasources** tab. If only **View** permission is set then the users of the group will only be able to connect to the allowed datasource, and if only **Edit** permission is set then the users of the group will be able to update the credentials of the allowed datasources. +
+ + Datasources: Overview + +
+ + - If any of the permission(View or Edit) is not enabled for a user group then the users of the group will get an error toast when they try to Add or Delete the global datasource. +
+ + Datasources: Overview + +
-:::info -ToolJet allows you to transform the data returned by datasources using **[Transformations](/docs/tutorial/transformations)** -::: diff --git a/docs/versioned_docs/version-2.3.0/how-to/intentionally-fail-js-query.md b/docs/versioned_docs/version-2.3.0/how-to/intentionally-fail-js-query.md new file mode 100644 index 0000000000..bc7750ec4c --- /dev/null +++ b/docs/versioned_docs/version-2.3.0/how-to/intentionally-fail-js-query.md @@ -0,0 +1,23 @@ +--- +id: intentionally-fail-js-query +title: Intentionally fail a RunJS query +--- + +In this how-to guide, we will create a RunJS query that will throw an error. + +- Create a RunJS query and paste the code below. We will use the constructor `ReferenceError` since it is used to create a range error instance. + ```js + throw new ReferenceError('This is a reference error.'); + ``` + +- Now, add a event handler to show an alert when the query fails. **Save** the query and **Run** it. + +
+ + Intentionally fail a RunJS query + +
+ +:::info +Most common use-case for intentionally failing a query is **debugging**. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/how-to/use-s3-presigned-url-to-upload-docs.md b/docs/versioned_docs/version-2.3.0/how-to/use-s3-presigned-url-to-upload-docs.md new file mode 100644 index 0000000000..61dd6448d3 --- /dev/null +++ b/docs/versioned_docs/version-2.3.0/how-to/use-s3-presigned-url-to-upload-docs.md @@ -0,0 +1,173 @@ +--- +id: use-s3-signed-url-to-upload-docs +title: Use S3 signed URL to upload documents +--- + +# Use S3 signed URL to upload documents + +In this how-to guide, you'll learn to upload documents to S3 buckets using the **S3 signed URL** from a ToolJet application. + +For this guide, We are going to use one of the existing templates on ToolJet: **S3 File explorer** + +:::info using Templates +On ToolJet Dashboard, Click on the down arrow on the right of the **New App** button, from the dropdown choose the **Choose from template** option. +::: + +
+ +Use S3 pre-signed URL to upload documents: Choose template + +
+ +- Once you've created a new app using the template, you'll be prompted to create a **new version** of the existing version. After creating a new version, you'll be able to make changes in the app. + +
+ + Use S3 pre-signed URL to upload documents: new version + +
+ +- Go to the **datasource manager** on the left-sidebar, you'll find that the **AWS S3 datasource** is already added. All you need to do is update the datasource **credentials**. + + :::tip + Check the [AWS S3 datasource reference](/docs/data-sources/s3) to learn more about connnection and choosing your preferred authentication method. + ::: + +
+ + Use S3 pre-signed URL to upload documents: add datasource + +
+ +- Once the datasource is connected successfully, go to the query manager and **Run** the **getBuckets** query. The operation selected in the getBuckets query is **List Buckets** which will fetch an array of all the buckets. + +
+ + Use S3 pre-signed URL to upload documents: getBuckets query + +
+ +- Running the **getBuckets** query will load all the buckets in the dropdown in the app. + +
+ + Use S3 pre-signed URL to upload documents: loading buckets + +
+ +- Select a **bucket** from the dropdown and click on the **Fetch files** button to list all the files from the selected bucket on the table. The **Fetch files** button has the event handler added that triggers the **s32** query, the **s32** query uses the **List objects in a bucket** operation, and the bucket field in the query gets the value dynamically from the dropdown. + +
+ + Use S3 pre-signed URL to upload documents: list objects in a bucket + +
+ +- Let's go to the **uploadToS3** query and update the field values: + - **Operation**: Signed URL for upload + - **Bucket**: `{{components.dropdown1.value}}` this will fetch the dynamic value from the dropdown + - **Key**: `{{components.filepicker1.file[0].name}}` this will get the file name from the filepickers exposed variables + - **Expires in:** This sets an expiration time of URL, by default its `3600` seconds (1 hour) + - **Content Type**: `{{components.filepicker1.file[0].type}}` this will get the file type from the filepickers exposed variables + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Create two **RunJS** queries: + - Create a **runjs1** query and copy-paste the code below. This query gets the **base64data** from the file picker and convert the file's `base64Data` to into `BLOB`, and returns the file object. + ```js + const base64String = components.filepicker1.file[0].base64Data + const decodedArray = new Uint8Array(atob(base64String).split('').map(c => c.charCodeAt(0))); + const file = new Blob([decodedArray], { type: components.filepicker1.file[0].type }); + const fileName = components.filepicker1.file[0].name; + const fileObj = new File([file], fileName); + + return fileObj + ``` + +
+ + Use S3 pre-signed URL to upload documents + +
+ + - Create another **runjs2** query and copy-paste the code below. This query gets the data(file object) returned by the first runjs query, the url returned by the **uploadToS3** query, and then makes PUT request. + ```js + const file = queries.runjs2.data + const url = queries.s31.data.url + + fetch(url, { + method: 'PUT', + body: file, + mode: 'cors', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json' + } + }) + .then(response => console.log('Upload successful!')) + .catch(error => console.error('Error uploading file:', error)); + ``` + :::warning Enable Cross Origin Resource Sharing(CORS) + - For the file to be uploaded successfully, you will need to add the CORS policies from the **Permissions** tab of your **Bucket** settings. Here's a sample CORS: + ```json + [ + { + "AllowedHeaders": [ + "*" + ], + "AllowedMethods": [ + "GET", + "PUT", + "POST" + ], + "AllowedOrigins": [ + "*" + ], + "ExposeHeaders": [] + } + ] + ``` + ::: + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Go to the **uploadToS3**, scroll down and add an event handler to the **uploadToS3** query. Select the **Query Success** event, **Run Query** as the action, and **runjs1** as the query to be triggered. **Save** the query. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Let's go to the **runjs1** query and add the event handler to run a query on query success event, similar to how we did in the previous step. In the event handler, choose **runjs2** query. **Save** the query. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Now, let's go the final query **copySignedURL** that is connected to the table's action button. This query copy's the generated **Signed URL for download** onto the **clipboard**. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Now that we have updated all the queries, and connected them through the event handlers. We can go ahead and pick a file from the file picker. Click on the file picker, select a file and then hit the **Upload file to S3** button. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Once the button is clicked, the **uploadToS3** will triggered along with the **runjs1** and **runjs2** queries in sequence since we added them in the event handlers. + +- You can go to the table and click on the **Copy signed URL** action button on the table, this will trigger the **copySignedURL** query and will copy the URL on the clipboard. You can go to another tab and paste the URL to open the file on the browser. + diff --git a/docs/versioned_docs/version-2.3.0/org-management/permissions.md b/docs/versioned_docs/version-2.3.0/org-management/permissions.md index 5e33f5cb2c..b13433e0ef 100644 --- a/docs/versioned_docs/version-2.3.0/org-management/permissions.md +++ b/docs/versioned_docs/version-2.3.0/org-management/permissions.md @@ -8,7 +8,7 @@ Permissions allow you to create and share resources to easily ensure what level Admins can invite **Users** to their workspaces and assign them to the **Groups** that have Permissions to access Apps, folders, or workspace variables. :::info -See **[Manage Users and Groups](/docs/tutorial/manage-users-groups)** to learn how to invite users to ToolJet. +See **[Manage Users and Groups](/docs/tutorial/manage-users-groups)** to know more about managing users and groups on your workspace. ::: ## Role-Based Access Control (RBAC) Glossary @@ -18,4 +18,4 @@ See **[Manage Users and Groups](/docs/tutorial/manage-users-groups)** to learn h - **All Users** - Contains all the users in your workspace. When **New Users** are invited they are added to this group by default. - **Admins** - Contains all Admins in your workspace. Everyone added to this group will Permission to access all the ToolJet resources. - **Apps, Folder, Workspace Variables -** Resources that Admins can set permissions on. -- **Permissions -** Create, Update and Delete. +- **Permissions -** Create, Update and Delete. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/tutorial/manage-users-groups.md b/docs/versioned_docs/version-2.3.0/tutorial/manage-users-groups.md index 95991fd7fa..5877d2f075 100644 --- a/docs/versioned_docs/version-2.3.0/tutorial/manage-users-groups.md +++ b/docs/versioned_docs/version-2.3.0/tutorial/manage-users-groups.md @@ -77,7 +77,7 @@ Similar to archiving a user's access, you can enable it again by clicking on **U ## Managing Groups -On ToolJet, Admins can create groups for users added in a workspace and grant them access to particular app(s) with specific permissions. To manage groups, just go to the **Workspace Settings** from the left-sidebar of the dashboard and click on the **Groups**. +On ToolJet, Admins and Super Admins can create groups for users added in a workspace and grant them access to particular app(s) with specific permissions. To manage groups, just go to the **Workspace Settings** from the left-sidebar of the dashboard and click on the **Groups**.
@@ -87,11 +87,16 @@ On ToolJet, Admins can create groups for users added in a workspace and grant th ### Group properties -Every group on ToolJet has three sections: +Every group on ToolJet has **four** sections: + +- [Apps](#apps) +- [Users](#users) +- [Permissions](#permissions) +- [Datasources](#datasources) #### Apps: -Admins can add or remove any number of apps for a group of users. To add an app to a group, select an app from the dropdown and click on `Add` button next to it. You can also set app permissions such as `View` or `Edit` for the group. You can set different permissions for different apps in a group. +Admins and Super Admins can add or remove any number of apps for a group of users. To add an app to a group, select an app from the dropdown and click on `Add` button next to it. You can also set app permissions such as `View` or `Edit` for the group. You can set different permissions for different apps in a group.
@@ -101,7 +106,7 @@ Admins can add or remove any number of apps for a group of users. To add an app #### Users: -Admins can add or remove any numbers of users in a group. Just select a user from the dropdown and click on `Add` button to add it to a group. To delete a user from a group, click on `Delete` button next to it. +Admins and Super Admins can add or remove any numbers of users in a group. Just select a user from the dropdown and click on `Add` button to add it to a group. To delete a user from a group, click on `Delete` button next to it.
@@ -111,16 +116,30 @@ Admins can add or remove any numbers of users in a group. Just select a user fro #### Permissions: -Admins can set granular permission like creating/deleting apps or creating folder for a group of users. +Admins and Super Admins can set granular permission for the users added in that particular group, such as: +- **Create** and **Delete** Apps +- **Create**, **Update**, and **Delete** Folders +- **Create**, **Update**, and **Delete** [Workspace Variables](/docs/tutorial/workspace-variables) +- **Create** and **Delete** [Global Datasources](/docs/data-sources/overview)
-permissions +permissions + +
+ +#### Datasources: + +Only Admins and Super Admins can define what datasources can be **viewed** or **edited** by the users of that group. + +
+ +permissions
:::tip -All the activities performed by any Admin or any user in a workspace is logged in `Audit logs` - including any activity related with managing users and groups. +All the activities performed by any Admin, Super Admin or any user in a workspace is logged in [Audit logs](/docs/Enterprise/audit_logs) - including any activity related with managing users and groups. ::: ### Predefined Groups diff --git a/docs/versioned_docs/version-2.4.0/Enterprise/superadmin.md b/docs/versioned_docs/version-2.4.0/Enterprise/superadmin.md index 63262b98e9..43332fd236 100644 --- a/docs/versioned_docs/version-2.4.0/Enterprise/superadmin.md +++ b/docs/versioned_docs/version-2.4.0/Enterprise/superadmin.md @@ -17,6 +17,7 @@ The user details entered while setting up ToolJet will have Super Admin privileg | Manage Groups in their workspace (Create Group/Add or Delete Users from groups/ Modify Group Permissions) | ✅ | ✅ | | Manage SSO in their workspace | ✅ | ✅ | | Manage Workspace Variables in their workspace | ✅ | ✅ | +| [Manage Global datasources for the user group in their workspace](/docs/data-sources/overview#permissions) | ✅ | ✅ | | [Access any user's personal workspace (create, edit or delete apps)](#access-any-workspace) | ❌ | ✅ | | [Archive Admin or any user of any workspace](#archiveunarchive-users) | ❌ | ✅ | | [Access any user's ToolJet database (create, edit or delete database)](#access-tooljet-db-in-any-workspace) | ❌ | ✅ | diff --git a/docs/versioned_docs/version-2.4.0/contributing-guide/troubleshooting/eslint.md b/docs/versioned_docs/version-2.4.0/contributing-guide/troubleshooting/eslint.md index 465ba553c3..efb8d897fe 100644 --- a/docs/versioned_docs/version-2.4.0/contributing-guide/troubleshooting/eslint.md +++ b/docs/versioned_docs/version-2.4.0/contributing-guide/troubleshooting/eslint.md @@ -41,3 +41,6 @@ For VSCode users, you can set the formatter to `ESLint` in the [**settings.json* 1. **Node version 18.3.0** 2. **npm version 8.11.0** +:::tip +It is recommended to check the VSCode **Setting.json**(Press `ctrl/cmnd + P` and search `>Settings (JSON)`) file to ensure there are no overrides to the eslint config rules. Comment the following rules for eslint: **eslint.options: {...}**. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/data-sources/overview.md b/docs/versioned_docs/version-2.4.0/data-sources/overview.md index ec7527f8af..64eefc507c 100644 --- a/docs/versioned_docs/version-2.4.0/data-sources/overview.md +++ b/docs/versioned_docs/version-2.4.0/data-sources/overview.md @@ -3,9 +3,13 @@ id: overview title: Overview --- -# Datasources : Overview +# Global Datasources : Overview -Datasources pull in and push data to any source including databases, external APIs, or services. +Global datasources pull in and push data to any source including databases, external APIs, or services. Once a global datasource is connected to a workspace, the connection can be shared with any app of that workspace. + +:::caution +Global datasources are available only on **ToolJet version 2.3.0 and above**. +:::
@@ -13,30 +17,115 @@ Datasources pull in and push data to any source including databases, external AP
-## Connecting datasources +## Connecting global datasources -1. After logging in to ToolJet, create a new app from the dashboard +1. From the ToolJet dashboard, go to the **global datasources page** from the left sidebar. +
-2. There are two ways for connecting a datasource. You can connect from: - 1. **Left-sidebar**: On the left sidebar, click on the `datasource` icon and then click on the `+ add datasource` button + Datasources: Overview -
+
- Datasources: Overview +2. Click on the **Add new datasource** button, a modal will pop-up with all the available global datasources. +
-
+ Datasources: Overview - 2. **Query Panel**: Go to the query panel at the bottom, click on the `+Add` button and then click `Add datasource` button +
-
+3. Select the datasource, enter the **Credentials** and **Save** the datasource. +
- Datasources: Overview + Datasources: Overview -
+
-3. Follow the steps in the **[Datasource Library](/docs/data-sources/airtable)** specific to the desired datasource +4. Now, go back to the dashboard, create a new app, and the datasource will be available on the query panel under **Global Datasources**. Added datasources will be available on any of the **existing** or the **new applications**. +
+ + Datasources: Overview + +
+ +5. You can now create queries of the connected global datasource. From the queries, you'll be able to switch to **different connections** of the same datasource if there are more than one connections created. +
+ + Datasources: Overview + +
+ +## Changing scope of datasources of an app created on older versions of ToolJet + +On ToolJet versions below 2.3.0, the datasource connection was made from within the individual apps. To make it backward compatible, we added an option to change the scope of the datasources and make it global datasource. + +1. If you open an app created on previos versions of ToolJet, you'll find the datasource manager on the left sidebar of the App Builder. +
+ + Datasources: Overview + +
+ +2. Click on the kebab menu next to the connected datasource, select the **change scope** option. +
+ + Datasources: Overview + +
+ +3. Once you change the scope of the datasource and make it global, you'll see that the **datasource manager** is removed from the left sidebar and now you'll find the datasource on the **query panel** under Global Datasources. You can now configure the datasource fromt the Global Datasource page on the **dashboard**. +
+ + Datasources: Overview + +
+ + +## Default datasources + +By default, 4 datasources will be available on every app on ToolJet: +- **[ToolJet Database](/docs/tooljet-database/)** +- **[RestAPI](/docs/data-sources/restapi/)** +- **[Run JavaScript Query](/docs/data-sources/run-js/)** +- **[Run Python Query](/docs/data-sources/run-py/)** + +
+ + Datasources: Overview + +
+ +## Permissions + +Only **Admins** and **[Super Admins](/docs/Enterprise/superadmin)** of the workspace can change the **[Permissions](/docs/tutorial/manage-users-groups#group-properties)** for Global Datasource. + +From **Workspace Settings** -> **Groups Settings**, Admins and Super Admins can set the permission for a user group to: + +- **Create** and **Delete** datasources onto that workspace. If **Create** permission is enabled then the users can add new global datasources and **edit** the datasources as well but cannot **delete** it, and if only **Delete** permission is set then the users of the group will only be able to delete the connected datasources on the workspace. +
+ + Datasources: Overview + +
+ + - If any of the permission(Create or Delete) is not enabled for a user group then the users of the group will get an error toast when they try to Add or Delete the global datasource. +
+ + Datasources: Overview + +
+ +- **View** or **Edit** allowed global datasources from the **Datasources** tab. If only **View** permission is set then the users of the group will only be able to connect to the allowed datasource, and if only **Edit** permission is set then the users of the group will be able to update the credentials of the allowed datasources. +
+ + Datasources: Overview + +
+ + - If any of the permission(View or Edit) is not enabled for a user group then the users of the group will get an error toast when they try to Add or Delete the global datasource. +
+ + Datasources: Overview + +
-:::info -ToolJet allows you to transform the data returned by datasources using **[Transformations](/docs/tutorial/transformations)** -::: diff --git a/docs/versioned_docs/version-2.4.0/getting-started.md b/docs/versioned_docs/version-2.4.0/getting-started.md index 3d944f62c9..42699a3b53 100644 --- a/docs/versioned_docs/version-2.4.0/getting-started.md +++ b/docs/versioned_docs/version-2.4.0/getting-started.md @@ -143,7 +143,7 @@ Learn more about the **[ToolJet Database here](/docs/tooljet-database)**
:::info -ToolJet application's User interface is constructed using Components like Tables, Forms, Charts, or Buttons etc. Check **Components Catalog** to learn more. +ToolJet application's User interface is constructed using Components like Tables, Forms, Charts, or Buttons etc. Check **[Components Catalog](/docs/widgets/overview)** to learn more. ::: ### Build queries and bind data to UI @@ -156,7 +156,7 @@ ToolJet application's User interface is constructed using Components like Tables
:::info - ToolJet can connect to several databases, APIs and external services to fetch and modify data. Check **Datasource Catalog** to learn more. + ToolJet can connect to several databases, APIs and external services to fetch and modify data. Check **[Datasource Catalog](/docs/data-sources/overview)** to learn more. ::: 2. Choose a **Table** from the dropdown, Select the **List rows** option from the **Operation** dropdown, You can leave other query parameters. Scroll down and enable **Run this query on application load** - this will trigger the query when the app is loaded. @@ -199,8 +199,8 @@ ToolJet application's User interface is constructed using Components like Tables
:::info -- You can manipulate the data returned by the queries using **Transformations** -- You can also **Run JS query** or **Python query** to perform custom behavior inside ToolJet +- You can manipulate the data returned by the queries using **[Transformations](/docs/tutorial/transformations)** +- You can also **[Run JavaScript code](/docs/data-sources/run-js)** or **[Run Python code](/docs/data-sources/run-py)** to perform custom behavior inside ToolJet ::: ### Preview, Release and Share app @@ -210,7 +210,7 @@ ToolJet application's User interface is constructed using Components like Tables 3. **Share** option allows you to share the **released version** of the application with other users or you can also make the app **public** and anyone with the URL will be able to use the app. :::tip -You can control how much access to users have to your ToolJet apps and resources using **Org Management**. +You can control how much access to users have to your ToolJet apps and resources using **[Org Management](/docs/tutorial/manage-users-groups)**. ::: ## What Can I Do With ToolJet diff --git a/docs/versioned_docs/version-2.4.0/how-to/intentionally-fail-js-query.md b/docs/versioned_docs/version-2.4.0/how-to/intentionally-fail-js-query.md new file mode 100644 index 0000000000..bc7750ec4c --- /dev/null +++ b/docs/versioned_docs/version-2.4.0/how-to/intentionally-fail-js-query.md @@ -0,0 +1,23 @@ +--- +id: intentionally-fail-js-query +title: Intentionally fail a RunJS query +--- + +In this how-to guide, we will create a RunJS query that will throw an error. + +- Create a RunJS query and paste the code below. We will use the constructor `ReferenceError` since it is used to create a range error instance. + ```js + throw new ReferenceError('This is a reference error.'); + ``` + +- Now, add a event handler to show an alert when the query fails. **Save** the query and **Run** it. + +
+ + Intentionally fail a RunJS query + +
+ +:::info +Most common use-case for intentionally failing a query is **debugging**. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/how-to/use-s3-presigned-url-to-upload-docs.md b/docs/versioned_docs/version-2.4.0/how-to/use-s3-presigned-url-to-upload-docs.md new file mode 100644 index 0000000000..61dd6448d3 --- /dev/null +++ b/docs/versioned_docs/version-2.4.0/how-to/use-s3-presigned-url-to-upload-docs.md @@ -0,0 +1,173 @@ +--- +id: use-s3-signed-url-to-upload-docs +title: Use S3 signed URL to upload documents +--- + +# Use S3 signed URL to upload documents + +In this how-to guide, you'll learn to upload documents to S3 buckets using the **S3 signed URL** from a ToolJet application. + +For this guide, We are going to use one of the existing templates on ToolJet: **S3 File explorer** + +:::info using Templates +On ToolJet Dashboard, Click on the down arrow on the right of the **New App** button, from the dropdown choose the **Choose from template** option. +::: + +
+ +Use S3 pre-signed URL to upload documents: Choose template + +
+ +- Once you've created a new app using the template, you'll be prompted to create a **new version** of the existing version. After creating a new version, you'll be able to make changes in the app. + +
+ + Use S3 pre-signed URL to upload documents: new version + +
+ +- Go to the **datasource manager** on the left-sidebar, you'll find that the **AWS S3 datasource** is already added. All you need to do is update the datasource **credentials**. + + :::tip + Check the [AWS S3 datasource reference](/docs/data-sources/s3) to learn more about connnection and choosing your preferred authentication method. + ::: + +
+ + Use S3 pre-signed URL to upload documents: add datasource + +
+ +- Once the datasource is connected successfully, go to the query manager and **Run** the **getBuckets** query. The operation selected in the getBuckets query is **List Buckets** which will fetch an array of all the buckets. + +
+ + Use S3 pre-signed URL to upload documents: getBuckets query + +
+ +- Running the **getBuckets** query will load all the buckets in the dropdown in the app. + +
+ + Use S3 pre-signed URL to upload documents: loading buckets + +
+ +- Select a **bucket** from the dropdown and click on the **Fetch files** button to list all the files from the selected bucket on the table. The **Fetch files** button has the event handler added that triggers the **s32** query, the **s32** query uses the **List objects in a bucket** operation, and the bucket field in the query gets the value dynamically from the dropdown. + +
+ + Use S3 pre-signed URL to upload documents: list objects in a bucket + +
+ +- Let's go to the **uploadToS3** query and update the field values: + - **Operation**: Signed URL for upload + - **Bucket**: `{{components.dropdown1.value}}` this will fetch the dynamic value from the dropdown + - **Key**: `{{components.filepicker1.file[0].name}}` this will get the file name from the filepickers exposed variables + - **Expires in:** This sets an expiration time of URL, by default its `3600` seconds (1 hour) + - **Content Type**: `{{components.filepicker1.file[0].type}}` this will get the file type from the filepickers exposed variables + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Create two **RunJS** queries: + - Create a **runjs1** query and copy-paste the code below. This query gets the **base64data** from the file picker and convert the file's `base64Data` to into `BLOB`, and returns the file object. + ```js + const base64String = components.filepicker1.file[0].base64Data + const decodedArray = new Uint8Array(atob(base64String).split('').map(c => c.charCodeAt(0))); + const file = new Blob([decodedArray], { type: components.filepicker1.file[0].type }); + const fileName = components.filepicker1.file[0].name; + const fileObj = new File([file], fileName); + + return fileObj + ``` + +
+ + Use S3 pre-signed URL to upload documents + +
+ + - Create another **runjs2** query and copy-paste the code below. This query gets the data(file object) returned by the first runjs query, the url returned by the **uploadToS3** query, and then makes PUT request. + ```js + const file = queries.runjs2.data + const url = queries.s31.data.url + + fetch(url, { + method: 'PUT', + body: file, + mode: 'cors', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json' + } + }) + .then(response => console.log('Upload successful!')) + .catch(error => console.error('Error uploading file:', error)); + ``` + :::warning Enable Cross Origin Resource Sharing(CORS) + - For the file to be uploaded successfully, you will need to add the CORS policies from the **Permissions** tab of your **Bucket** settings. Here's a sample CORS: + ```json + [ + { + "AllowedHeaders": [ + "*" + ], + "AllowedMethods": [ + "GET", + "PUT", + "POST" + ], + "AllowedOrigins": [ + "*" + ], + "ExposeHeaders": [] + } + ] + ``` + ::: + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Go to the **uploadToS3**, scroll down and add an event handler to the **uploadToS3** query. Select the **Query Success** event, **Run Query** as the action, and **runjs1** as the query to be triggered. **Save** the query. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Let's go to the **runjs1** query and add the event handler to run a query on query success event, similar to how we did in the previous step. In the event handler, choose **runjs2** query. **Save** the query. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Now, let's go the final query **copySignedURL** that is connected to the table's action button. This query copy's the generated **Signed URL for download** onto the **clipboard**. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Now that we have updated all the queries, and connected them through the event handlers. We can go ahead and pick a file from the file picker. Click on the file picker, select a file and then hit the **Upload file to S3** button. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- Once the button is clicked, the **uploadToS3** will triggered along with the **runjs1** and **runjs2** queries in sequence since we added them in the event handlers. + +- You can go to the table and click on the **Copy signed URL** action button on the table, this will trigger the **copySignedURL** query and will copy the URL on the clipboard. You can go to another tab and paste the URL to open the file on the browser. + diff --git a/docs/versioned_docs/version-2.4.0/org-management/permissions.md b/docs/versioned_docs/version-2.4.0/org-management/permissions.md index 5e33f5cb2c..b13433e0ef 100644 --- a/docs/versioned_docs/version-2.4.0/org-management/permissions.md +++ b/docs/versioned_docs/version-2.4.0/org-management/permissions.md @@ -8,7 +8,7 @@ Permissions allow you to create and share resources to easily ensure what level Admins can invite **Users** to their workspaces and assign them to the **Groups** that have Permissions to access Apps, folders, or workspace variables. :::info -See **[Manage Users and Groups](/docs/tutorial/manage-users-groups)** to learn how to invite users to ToolJet. +See **[Manage Users and Groups](/docs/tutorial/manage-users-groups)** to know more about managing users and groups on your workspace. ::: ## Role-Based Access Control (RBAC) Glossary @@ -18,4 +18,4 @@ See **[Manage Users and Groups](/docs/tutorial/manage-users-groups)** to learn h - **All Users** - Contains all the users in your workspace. When **New Users** are invited they are added to this group by default. - **Admins** - Contains all Admins in your workspace. Everyone added to this group will Permission to access all the ToolJet resources. - **Apps, Folder, Workspace Variables -** Resources that Admins can set permissions on. -- **Permissions -** Create, Update and Delete. +- **Permissions -** Create, Update and Delete. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/tutorial/manage-users-groups.md b/docs/versioned_docs/version-2.4.0/tutorial/manage-users-groups.md index 95991fd7fa..5877d2f075 100644 --- a/docs/versioned_docs/version-2.4.0/tutorial/manage-users-groups.md +++ b/docs/versioned_docs/version-2.4.0/tutorial/manage-users-groups.md @@ -77,7 +77,7 @@ Similar to archiving a user's access, you can enable it again by clicking on **U ## Managing Groups -On ToolJet, Admins can create groups for users added in a workspace and grant them access to particular app(s) with specific permissions. To manage groups, just go to the **Workspace Settings** from the left-sidebar of the dashboard and click on the **Groups**. +On ToolJet, Admins and Super Admins can create groups for users added in a workspace and grant them access to particular app(s) with specific permissions. To manage groups, just go to the **Workspace Settings** from the left-sidebar of the dashboard and click on the **Groups**.
@@ -87,11 +87,16 @@ On ToolJet, Admins can create groups for users added in a workspace and grant th ### Group properties -Every group on ToolJet has three sections: +Every group on ToolJet has **four** sections: + +- [Apps](#apps) +- [Users](#users) +- [Permissions](#permissions) +- [Datasources](#datasources) #### Apps: -Admins can add or remove any number of apps for a group of users. To add an app to a group, select an app from the dropdown and click on `Add` button next to it. You can also set app permissions such as `View` or `Edit` for the group. You can set different permissions for different apps in a group. +Admins and Super Admins can add or remove any number of apps for a group of users. To add an app to a group, select an app from the dropdown and click on `Add` button next to it. You can also set app permissions such as `View` or `Edit` for the group. You can set different permissions for different apps in a group.
@@ -101,7 +106,7 @@ Admins can add or remove any number of apps for a group of users. To add an app #### Users: -Admins can add or remove any numbers of users in a group. Just select a user from the dropdown and click on `Add` button to add it to a group. To delete a user from a group, click on `Delete` button next to it. +Admins and Super Admins can add or remove any numbers of users in a group. Just select a user from the dropdown and click on `Add` button to add it to a group. To delete a user from a group, click on `Delete` button next to it.
@@ -111,16 +116,30 @@ Admins can add or remove any numbers of users in a group. Just select a user fro #### Permissions: -Admins can set granular permission like creating/deleting apps or creating folder for a group of users. +Admins and Super Admins can set granular permission for the users added in that particular group, such as: +- **Create** and **Delete** Apps +- **Create**, **Update**, and **Delete** Folders +- **Create**, **Update**, and **Delete** [Workspace Variables](/docs/tutorial/workspace-variables) +- **Create** and **Delete** [Global Datasources](/docs/data-sources/overview)
-permissions +permissions + +
+ +#### Datasources: + +Only Admins and Super Admins can define what datasources can be **viewed** or **edited** by the users of that group. + +
+ +permissions
:::tip -All the activities performed by any Admin or any user in a workspace is logged in `Audit logs` - including any activity related with managing users and groups. +All the activities performed by any Admin, Super Admin or any user in a workspace is logged in [Audit logs](/docs/Enterprise/audit_logs) - including any activity related with managing users and groups. ::: ### Predefined Groups diff --git a/docs/versioned_sidebars/version-1.x.x-sidebars.json b/docs/versioned_sidebars/version-1.x.x-sidebars.json index 0753138ba2..cbc7da86dc 100644 --- a/docs/versioned_sidebars/version-1.x.x-sidebars.json +++ b/docs/versioned_sidebars/version-1.x.x-sidebars.json @@ -208,11 +208,13 @@ }, "items": [ "how-to/run-actions-from-runjs", + "how-to/intentionally-fail-js-query", "how-to/run-query-at-specified-intervals", "how-to/bulk-update-multiple-rows", "how-to/access-cellvalue-rowdata", "how-to/access-currentuser", "how-to/oauth2-authorization", + "how-to/use-s3-signed-url-to-upload-docs", "how-to/upload-files-aws", "how-to/upload-files-gcs", "how-to/access-users-location" @@ -321,4 +323,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/docs/versioned_sidebars/version-2.0.0-sidebars.json b/docs/versioned_sidebars/version-2.0.0-sidebars.json index bb0aa95d25..95044555c7 100644 --- a/docs/versioned_sidebars/version-2.0.0-sidebars.json +++ b/docs/versioned_sidebars/version-2.0.0-sidebars.json @@ -289,10 +289,12 @@ "how-to/bulk-update-multiple-rows", "how-to/access-currentuser", "how-to/use-axios-in-runjs", + "how-to/intentionally-fail-js-query", "how-to/import-external-libraries-using-runjs", "how-to/run-actions-from-runjs", "how-to/run-query-at-specified-intervals", "how-to/access-users-location", + "how-to/use-s3-signed-url-to-upload-docs", "how-to/s3-custom-endpoints", "how-to/oauth2-authorization", "how-to/upload-files-aws", diff --git a/docs/versioned_sidebars/version-2.1.0-sidebars.json b/docs/versioned_sidebars/version-2.1.0-sidebars.json index 54a661e6e3..6674ce70a3 100644 --- a/docs/versioned_sidebars/version-2.1.0-sidebars.json +++ b/docs/versioned_sidebars/version-2.1.0-sidebars.json @@ -290,10 +290,12 @@ "how-to/access-currentuser", "how-to/import-external-libraries-using-runjs", "how-to/use-axios-in-runjs", + "how-to/intentionally-fail-js-query", "how-to/import-external-libraries-using-runpy", "how-to/run-actions-from-runjs", "how-to/run-query-at-specified-intervals", "how-to/access-users-location", + "how-to/use-s3-signed-url-to-upload-docs", "how-to/s3-custom-endpoints", "how-to/oauth2-authorization", "how-to/upload-files-aws", diff --git a/docs/versioned_sidebars/version-2.2.0-sidebars.json b/docs/versioned_sidebars/version-2.2.0-sidebars.json index 1342f28433..041c69982f 100644 --- a/docs/versioned_sidebars/version-2.2.0-sidebars.json +++ b/docs/versioned_sidebars/version-2.2.0-sidebars.json @@ -290,11 +290,13 @@ "how-to/bulk-update-multiple-rows", "how-to/access-currentuser", "how-to/use-axios-in-runjs", + "how-to/intentionally-fail-js-query", "how-to/import-external-libraries-using-runpy", "how-to/import-external-libraries-using-runjs", "how-to/run-actions-from-runjs", "how-to/run-query-at-specified-intervals", "how-to/access-users-location", + "how-to/use-s3-signed-url-to-upload-docs", "how-to/s3-custom-endpoints", "how-to/oauth2-authorization", "how-to/upload-files-aws", diff --git a/docs/versioned_sidebars/version-2.3.0-sidebars.json b/docs/versioned_sidebars/version-2.3.0-sidebars.json index 1342f28433..ca12fb1f4c 100644 --- a/docs/versioned_sidebars/version-2.3.0-sidebars.json +++ b/docs/versioned_sidebars/version-2.3.0-sidebars.json @@ -293,8 +293,10 @@ "how-to/import-external-libraries-using-runpy", "how-to/import-external-libraries-using-runjs", "how-to/run-actions-from-runjs", + "how-to/intentionally-fail-js-query", "how-to/run-query-at-specified-intervals", "how-to/access-users-location", + "how-to/use-s3-signed-url-to-upload-docs", "how-to/s3-custom-endpoints", "how-to/oauth2-authorization", "how-to/upload-files-aws", diff --git a/docs/versioned_sidebars/version-2.4.0-sidebars.json b/docs/versioned_sidebars/version-2.4.0-sidebars.json index 96bdc2e3c6..6fb00ee9c3 100644 --- a/docs/versioned_sidebars/version-2.4.0-sidebars.json +++ b/docs/versioned_sidebars/version-2.4.0-sidebars.json @@ -293,9 +293,11 @@ "how-to/import-external-libraries-using-runpy", "how-to/import-external-libraries-using-runjs", "how-to/run-actions-from-runjs", + "how-to/intentionally-fail-js-query", "how-to/run-query-at-specified-intervals", "how-to/access-users-location", "how-to/s3-custom-endpoints", + "how-to/use-s3-signed-url-to-upload-docs", "how-to/oauth2-authorization", "how-to/upload-files-aws", "how-to/upload-files-gcs", @@ -367,4 +369,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/docs/yarn.lock b/docs/yarn.lock index 38d7aeb4db..d695257bf9 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -1196,84 +1196,7 @@ "@docsearch/css" "3.2.1" algoliasearch "^4.0.0" -"@docusaurus/core@2.0.1", "@docusaurus/core@^2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/core/-/core-2.0.1.tgz" - integrity sha512-Prd46TtZdiixlTl8a+h9bI5HegkfREjSNkrX2rVEwJZeziSz4ya+l7QDnbnCB2XbxEG8cveFo/F9q5lixolDtQ== - dependencies: - "@babel/core" "^7.18.6" - "@babel/generator" "^7.18.7" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-runtime" "^7.18.6" - "@babel/preset-env" "^7.18.6" - "@babel/preset-react" "^7.18.6" - "@babel/preset-typescript" "^7.18.6" - "@babel/runtime" "^7.18.6" - "@babel/runtime-corejs3" "^7.18.6" - "@babel/traverse" "^7.18.8" - "@docusaurus/cssnano-preset" "2.0.1" - "@docusaurus/logger" "2.0.1" - "@docusaurus/mdx-loader" "2.0.1" - "@docusaurus/react-loadable" "5.5.2" - "@docusaurus/utils" "2.0.1" - "@docusaurus/utils-common" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" - "@slorber/static-site-generator-webpack-plugin" "^4.0.7" - "@svgr/webpack" "^6.2.1" - autoprefixer "^10.4.7" - babel-loader "^8.2.5" - babel-plugin-dynamic-import-node "^2.3.3" - boxen "^6.2.1" - chalk "^4.1.2" - chokidar "^3.5.3" - clean-css "^5.3.0" - cli-table3 "^0.6.2" - combine-promises "^1.1.0" - commander "^5.1.0" - copy-webpack-plugin "^11.0.0" - core-js "^3.23.3" - css-loader "^6.7.1" - css-minimizer-webpack-plugin "^4.0.0" - cssnano "^5.1.12" - del "^6.1.1" - detect-port "^1.3.0" - escape-html "^1.0.3" - eta "^1.12.3" - file-loader "^6.2.0" - fs-extra "^10.1.0" - html-minifier-terser "^6.1.0" - html-tags "^3.2.0" - html-webpack-plugin "^5.5.0" - import-fresh "^3.3.0" - leven "^3.1.0" - lodash "^4.17.21" - mini-css-extract-plugin "^2.6.1" - postcss "^8.4.14" - postcss-loader "^7.0.0" - prompts "^2.4.2" - react-dev-utils "^12.0.1" - react-helmet-async "^1.3.0" - react-loadable "npm:@docusaurus/react-loadable@5.5.2" - react-loadable-ssr-addon-v5-slorber "^1.0.1" - react-router "^5.3.3" - react-router-config "^5.1.1" - react-router-dom "^5.3.3" - rtl-detect "^1.0.4" - semver "^7.3.7" - serve-handler "^6.1.3" - shelljs "^0.8.5" - terser-webpack-plugin "^5.3.3" - tslib "^2.4.0" - update-notifier "^5.1.0" - url-loader "^4.1.1" - wait-on "^6.0.1" - webpack "^5.73.0" - webpack-bundle-analyzer "^4.5.0" - webpack-dev-server "^4.9.3" - webpack-merge "^5.8.0" - webpackbar "^5.0.2" - -"@docusaurus/core@2.4.0": +"@docusaurus/core@2.4.0", "@docusaurus/core@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.4.0.tgz#a12c175cb2e5a7e4582e65876a50813f6168913d" integrity sha512-J55/WEoIpRcLf3afO5POHPguVZosKmJEQWKBL+K7TAnfuE7i+Y0NPLlkKtnWCehagGsgTqClfQEexH/UT4kELA== @@ -1350,16 +1273,6 @@ webpack-merge "^5.8.0" webpackbar "^5.0.2" -"@docusaurus/cssnano-preset@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.1.tgz" - integrity sha512-MCJ6rRmlqLmlCsZIoIxOxDb0rYzIPEm9PYpsBW+CGNnbk+x8xK+11hnrxzvXHqDRNpxrq3Kq2jYUmg/DkqE6vg== - dependencies: - cssnano-preset-advanced "^5.3.8" - postcss "^8.4.14" - postcss-sort-media-queries "^4.2.1" - tslib "^2.4.0" - "@docusaurus/cssnano-preset@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.4.0.tgz#9213586358e0cce517f614af041eb7d184f8add6" @@ -1370,14 +1283,6 @@ postcss-sort-media-queries "^4.2.1" tslib "^2.4.0" -"@docusaurus/logger@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.0.1.tgz" - integrity sha512-wIWseCKko1w/WARcDjO3N/XoJ0q/VE42AthP0eNAfEazDjJ94NXbaI6wuUsuY/bMg6hTKGVIpphjj2LoX3g6dA== - dependencies: - chalk "^4.1.2" - tslib "^2.4.0" - "@docusaurus/logger@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.4.0.tgz#393d91ad9ecdb9a8f80167dd6a34d4b45219b835" @@ -1386,29 +1291,6 @@ chalk "^4.1.2" tslib "^2.4.0" -"@docusaurus/mdx-loader@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.0.1.tgz" - integrity sha512-tdNeljdilXCmhbaEND3SAgsqaw/oh7v9onT5yrIrL26OSk2AFwd+MIi4R8jt8vq33M0R4rz2wpknm0fQIkDdvQ== - dependencies: - "@babel/parser" "^7.18.8" - "@babel/traverse" "^7.18.8" - "@docusaurus/logger" "2.0.1" - "@docusaurus/utils" "2.0.1" - "@mdx-js/mdx" "^1.6.22" - escape-html "^1.0.3" - file-loader "^6.2.0" - fs-extra "^10.1.0" - image-size "^1.0.1" - mdast-util-to-string "^2.0.0" - remark-emoji "^2.2.0" - stringify-object "^3.3.0" - tslib "^2.4.0" - unified "^9.2.2" - unist-util-visit "^2.0.3" - url-loader "^4.1.1" - webpack "^5.73.0" - "@docusaurus/mdx-loader@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.4.0.tgz#c6310342904af2f203e7df86a9df623f86840f2d" @@ -1432,13 +1314,13 @@ url-loader "^4.1.1" webpack "^5.73.0" -"@docusaurus/module-type-aliases@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-2.0.1.tgz" - integrity sha512-f888ylnxHAM/3T8p1lx08+lTc6/g7AweSRfRuZvrVhHXj3Tz/nTTxaP6gPTGkJK7WLqTagpar/IGP6/74IBbkg== +"@docusaurus/module-type-aliases@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-2.4.0.tgz#6961605d20cd46f86163ed8c2d83d438b02b4028" + integrity sha512-YEQO2D3UXs72qCn8Cr+RlycSQXVGN9iEUyuHwTuK4/uL/HFomB2FHSU0vSDM23oLd+X/KibQ3Ez6nGjQLqXcHg== dependencies: "@docusaurus/react-loadable" "5.5.2" - "@docusaurus/types" "2.0.1" + "@docusaurus/types" "2.4.0" "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router-config" "*" @@ -1446,18 +1328,18 @@ react-helmet-async "*" react-loadable "npm:@docusaurus/react-loadable@5.5.2" -"@docusaurus/plugin-content-blog@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.1.tgz" - integrity sha512-/4ua3iFYcpwgpeYgHnhVGROB/ybnauLH2+rICb4vz/+Gn1hjAmGXVYq1fk8g49zGs3uxx5nc0H5bL9P0g977IQ== +"@docusaurus/plugin-content-blog@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.4.0.tgz#50dbfbc7b51f152ae660385fd8b34076713374c3" + integrity sha512-YwkAkVUxtxoBAIj/MCb4ohN0SCtHBs4AS75jMhPpf67qf3j+U/4n33cELq7567hwyZ6fMz2GPJcVmctzlGGThQ== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/logger" "2.0.1" - "@docusaurus/mdx-loader" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils" "2.0.1" - "@docusaurus/utils-common" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/logger" "2.4.0" + "@docusaurus/mdx-loader" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils" "2.4.0" + "@docusaurus/utils-common" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" cheerio "^1.0.0-rc.12" feed "^4.2.2" fs-extra "^10.1.0" @@ -1468,18 +1350,18 @@ utility-types "^3.10.0" webpack "^5.73.0" -"@docusaurus/plugin-content-docs@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.1.tgz" - integrity sha512-2qeBWRy1EjgnXdwAO6/csDIS1UVNmhmtk/bQ2s9jqjpwM8YVgZ8QVdkxFAMWXgZWDQdwWwdP1rnmoEelE4HknQ== +"@docusaurus/plugin-content-docs@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.4.0.tgz#36e235adf902325735b873b4f535205884363728" + integrity sha512-ic/Z/ZN5Rk/RQo+Io6rUGpToOtNbtPloMR2JcGwC1xT2riMu6zzfSwmBi9tHJgdXH6CB5jG+0dOZZO8QS5tmDg== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/logger" "2.0.1" - "@docusaurus/mdx-loader" "2.0.1" - "@docusaurus/module-type-aliases" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/logger" "2.4.0" + "@docusaurus/mdx-loader" "2.4.0" + "@docusaurus/module-type-aliases" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" "@types/react-router-config" "^5.0.6" combine-promises "^1.1.0" fs-extra "^10.1.0" @@ -1490,68 +1372,63 @@ utility-types "^3.10.0" webpack "^5.73.0" -"@docusaurus/plugin-content-pages@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.1.tgz" - integrity sha512-6apSVeJENnNecAH5cm5VnRqR103M6qSI6IuiP7tVfD5H4AWrfDNkvJQV2+R2PIq3bGrwmX4fcXl1x4g0oo7iwA== +"@docusaurus/plugin-content-pages@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.4.0.tgz#6169909a486e1eae0ddffff0b1717ce4332db4d4" + integrity sha512-Pk2pOeOxk8MeU3mrTU0XLIgP9NZixbdcJmJ7RUFrZp1Aj42nd0RhIT14BGvXXyqb8yTQlk4DmYGAzqOfBsFyGw== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/mdx-loader" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/mdx-loader" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" fs-extra "^10.1.0" tslib "^2.4.0" webpack "^5.73.0" -"@docusaurus/plugin-debug@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.0.1.tgz" - integrity sha512-jpZBT5HK7SWx1LRQyv9d14i44vSsKXGZsSPA2ndth5HykHJsiAj9Fwl1AtzmtGYuBmI+iXQyOd4MAMHd4ZZ1tg== +"@docusaurus/plugin-debug@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.4.0.tgz#1ad513fe9bcaf017deccf62df8b8843faeeb7d37" + integrity sha512-KC56DdYjYT7Txyux71vXHXGYZuP6yYtqwClvYpjKreWIHWus5Zt6VNi23rMZv3/QKhOCrN64zplUbdfQMvddBQ== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils" "2.4.0" fs-extra "^10.1.0" react-json-view "^1.21.3" tslib "^2.4.0" -"@docusaurus/plugin-google-analytics@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.1.tgz" - integrity sha512-d5qb+ZeQcg1Czoxc+RacETjLdp2sN/TAd7PGN/GrvtijCdgNmvVAtZ9QgajBTG0YbJFVPTeZ39ad2bpoOexX0w== +"@docusaurus/plugin-google-analytics@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.4.0.tgz#8062d7a09d366329dfd3ce4e8a619da8624b6cc3" + integrity sha512-uGUzX67DOAIglygdNrmMOvEp8qG03X20jMWadeqVQktS6nADvozpSLGx4J0xbkblhJkUzN21WiilsP9iVP+zkw== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" tslib "^2.4.0" -"@docusaurus/plugin-google-gtag@2.0.1", "@docusaurus/plugin-google-gtag@^2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.1.tgz" - integrity sha512-qiRufJe2FvIyzICbkjm4VbVCI1hyEju/CebfDKkKh2ZtV4q6DM1WZG7D6VoQSXL8MrMFB895gipOM4BwdM8VsQ== +"@docusaurus/plugin-google-gtag@2.4.0", "@docusaurus/plugin-google-gtag@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.4.0.tgz#a8efda476f971410dfb3aab1cfe1f0f7d269adc5" + integrity sha512-adj/70DANaQs2+TF/nRdMezDXFAV/O/pjAbUgmKBlyOTq5qoMe0Tk4muvQIwWUmiUQxFJe+sKlZGM771ownyOg== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" tslib "^2.4.0" -"@docusaurus/plugin-sitemap@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.1.tgz" - integrity sha512-KcYuIUIp2JPzUf+Xa7W2BSsjLgN1/0h+VAz7D/C3RYjAgC5ApPX8wO+TECmGfunl/m7WKGUmLabfOon/as64kQ== +"@docusaurus/plugin-google-tag-manager@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-2.4.0.tgz#9a94324ac496835fc34e233cc60441df4e04dfdd" + integrity sha512-E66uGcYs4l7yitmp/8kMEVQftFPwV9iC62ORh47Veqzs6ExwnhzBkJmwDnwIysHBF1vlxnzET0Fl2LfL5fRR3A== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/logger" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils" "2.0.1" - "@docusaurus/utils-common" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" - fs-extra "^10.1.0" - sitemap "^7.1.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" tslib "^2.4.0" -"@docusaurus/plugin-sitemap@^2.2.0": +"@docusaurus/plugin-sitemap@2.4.0", "@docusaurus/plugin-sitemap@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.4.0.tgz#ba0eb43565039fe011bdd874b5c5d7252b19d709" integrity sha512-pZxh+ygfnI657sN8a/FkYVIAmVv0CGk71QMKqJBOfMmDHNN1FeDeFkBjWP49ejBqpqAhjufkv5UWq3UOu2soCw== @@ -1566,23 +1443,24 @@ sitemap "^7.1.1" tslib "^2.4.0" -"@docusaurus/preset-classic@^2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.0.1.tgz" - integrity sha512-nOoniTg46My1qdDlLWeFs55uEmxOJ+9WMF8KKG8KMCu5LAvpemMi7rQd4x8Tw+xiPHZ/sQzH9JmPTMPRE4QGPw== +"@docusaurus/preset-classic@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.4.0.tgz#92fdcfab35d8d0ffb8c38bcbf439e4e1cb0566a3" + integrity sha512-/5z5o/9bc6+P5ool2y01PbJhoGddEGsC0ej1MF6mCoazk8A+kW4feoUd68l7Bnv01rCnG3xy7kHUQP97Y0grUA== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/plugin-content-blog" "2.0.1" - "@docusaurus/plugin-content-docs" "2.0.1" - "@docusaurus/plugin-content-pages" "2.0.1" - "@docusaurus/plugin-debug" "2.0.1" - "@docusaurus/plugin-google-analytics" "2.0.1" - "@docusaurus/plugin-google-gtag" "2.0.1" - "@docusaurus/plugin-sitemap" "2.0.1" - "@docusaurus/theme-classic" "2.0.1" - "@docusaurus/theme-common" "2.0.1" - "@docusaurus/theme-search-algolia" "2.0.1" - "@docusaurus/types" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/plugin-content-blog" "2.4.0" + "@docusaurus/plugin-content-docs" "2.4.0" + "@docusaurus/plugin-content-pages" "2.4.0" + "@docusaurus/plugin-debug" "2.4.0" + "@docusaurus/plugin-google-analytics" "2.4.0" + "@docusaurus/plugin-google-gtag" "2.4.0" + "@docusaurus/plugin-google-tag-manager" "2.4.0" + "@docusaurus/plugin-sitemap" "2.4.0" + "@docusaurus/theme-classic" "2.4.0" + "@docusaurus/theme-common" "2.4.0" + "@docusaurus/theme-search-algolia" "2.4.0" + "@docusaurus/types" "2.4.0" "@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": version "5.5.2" @@ -1592,27 +1470,27 @@ "@types/react" "*" prop-types "^15.6.2" -"@docusaurus/theme-classic@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.0.1.tgz" - integrity sha512-0jfigiqkUwIuKOw7Me5tqUM9BBvoQX7qqeevx7v4tkYQexPhk3VYSZo7aRuoJ9oyW5makCTPX551PMJzmq7+sw== +"@docusaurus/theme-classic@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.4.0.tgz#a5404967b00adec3472efca4c3b3f6a5e2021c78" + integrity sha512-GMDX5WU6Z0OC65eQFgl3iNNEbI9IMJz9f6KnOyuMxNUR6q0qVLsKCNopFUDfFNJ55UU50o7P7o21yVhkwpfJ9w== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/mdx-loader" "2.0.1" - "@docusaurus/module-type-aliases" "2.0.1" - "@docusaurus/plugin-content-blog" "2.0.1" - "@docusaurus/plugin-content-docs" "2.0.1" - "@docusaurus/plugin-content-pages" "2.0.1" - "@docusaurus/theme-common" "2.0.1" - "@docusaurus/theme-translations" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils" "2.0.1" - "@docusaurus/utils-common" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/mdx-loader" "2.4.0" + "@docusaurus/module-type-aliases" "2.4.0" + "@docusaurus/plugin-content-blog" "2.4.0" + "@docusaurus/plugin-content-docs" "2.4.0" + "@docusaurus/plugin-content-pages" "2.4.0" + "@docusaurus/theme-common" "2.4.0" + "@docusaurus/theme-translations" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils" "2.4.0" + "@docusaurus/utils-common" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" "@mdx-js/react" "^1.6.22" clsx "^1.2.1" copy-text-to-clipboard "^3.0.1" - infima "0.2.0-alpha.42" + infima "0.2.0-alpha.43" lodash "^4.17.21" nprogress "^0.2.0" postcss "^8.4.14" @@ -1623,17 +1501,18 @@ tslib "^2.4.0" utility-types "^3.10.0" -"@docusaurus/theme-common@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.0.1.tgz" - integrity sha512-I3b6e/ryiTQMsbES40cP0DRGnfr0E2qghVq+XecyMKjBPejISoSFEDn0MsnbW8Q26k1Dh/0qDH8QKDqaZZgLhA== +"@docusaurus/theme-common@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.4.0.tgz#626096fe9552d240a2115b492c7e12099070cf2d" + integrity sha512-IkG/l5f/FLY6cBIxtPmFnxpuPzc5TupuqlOx+XDN+035MdQcAh8wHXXZJAkTeYDeZ3anIUSUIvWa7/nRKoQEfg== dependencies: - "@docusaurus/mdx-loader" "2.0.1" - "@docusaurus/module-type-aliases" "2.0.1" - "@docusaurus/plugin-content-blog" "2.0.1" - "@docusaurus/plugin-content-docs" "2.0.1" - "@docusaurus/plugin-content-pages" "2.0.1" - "@docusaurus/utils" "2.0.1" + "@docusaurus/mdx-loader" "2.4.0" + "@docusaurus/module-type-aliases" "2.4.0" + "@docusaurus/plugin-content-blog" "2.4.0" + "@docusaurus/plugin-content-docs" "2.4.0" + "@docusaurus/plugin-content-pages" "2.4.0" + "@docusaurus/utils" "2.4.0" + "@docusaurus/utils-common" "2.4.0" "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router-config" "*" @@ -1641,52 +1520,39 @@ parse-numeric-range "^1.3.0" prism-react-renderer "^1.3.5" tslib "^2.4.0" + use-sync-external-store "^1.2.0" utility-types "^3.10.0" -"@docusaurus/theme-search-algolia@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.1.tgz" - integrity sha512-cw3NaOSKbYlsY6uNj4PgO+5mwyQ3aEWre5RlmvjStaz2cbD15Nr69VG8Rd/F6Q5VsCT8BvSdkPDdDG5d/ACexg== +"@docusaurus/theme-search-algolia@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.4.0.tgz#07d297d50c44446d6bc5a37be39afb8f014084e1" + integrity sha512-pPCJSCL1Qt4pu/Z0uxBAuke0yEBbxh0s4fOvimna7TEcBLPq0x06/K78AaABXrTVQM6S0vdocFl9EoNgU17hqA== dependencies: "@docsearch/react" "^3.1.1" - "@docusaurus/core" "2.0.1" - "@docusaurus/logger" "2.0.1" - "@docusaurus/plugin-content-docs" "2.0.1" - "@docusaurus/theme-common" "2.0.1" - "@docusaurus/theme-translations" "2.0.1" - "@docusaurus/utils" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/logger" "2.4.0" + "@docusaurus/plugin-content-docs" "2.4.0" + "@docusaurus/theme-common" "2.4.0" + "@docusaurus/theme-translations" "2.4.0" + "@docusaurus/utils" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" algoliasearch "^4.13.1" algoliasearch-helper "^3.10.0" clsx "^1.2.1" - eta "^1.12.3" + eta "^2.0.0" fs-extra "^10.1.0" lodash "^4.17.21" tslib "^2.4.0" utility-types "^3.10.0" -"@docusaurus/theme-translations@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.0.1.tgz" - integrity sha512-v1MYYlbsdX+rtKnXFcIAn9ar0Z6K0yjqnCYS0p/KLCLrfJwfJ8A3oRJw2HiaIb8jQfk1WMY2h5Qi1p4vHOekQw== +"@docusaurus/theme-translations@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.4.0.tgz#62dacb7997322f4c5a828b3ab66177ec6769eb33" + integrity sha512-kEoITnPXzDPUMBHk3+fzEzbopxLD3fR5sDoayNH0vXkpUukA88/aDL1bqkhxWZHA3LOfJ3f0vJbOwmnXW5v85Q== dependencies: fs-extra "^10.1.0" tslib "^2.4.0" -"@docusaurus/types@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/types/-/types-2.0.1.tgz" - integrity sha512-o+4hAFWkj3sBszVnRTAnNqtAIuIW0bNaYyDwQhQ6bdz3RAPEq9cDKZxMpajsj4z2nRty8XjzhyufAAjxFTyrfg== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - commander "^5.1.0" - joi "^17.6.0" - react-helmet-async "^1.3.0" - utility-types "^3.10.0" - webpack "^5.73.0" - webpack-merge "^5.8.0" - "@docusaurus/types@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.4.0.tgz#f94f89a0253778b617c5d40ac6f16b17ec55ce41" @@ -1701,13 +1567,6 @@ webpack "^5.73.0" webpack-merge "^5.8.0" -"@docusaurus/utils-common@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.0.1.tgz" - integrity sha512-kajCCDCXRd1HFH5EUW31MPaQcsyNlGakpkDoTBtBvpa4EIPvWaSKy7TIqYKHrZjX4tnJ0YbEJvaXfjjgdq5xSg== - dependencies: - tslib "^2.4.0" - "@docusaurus/utils-common@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.4.0.tgz#eb2913871860ed32e73858b4c7787dd820c5558d" @@ -1715,17 +1574,6 @@ dependencies: tslib "^2.4.0" -"@docusaurus/utils-validation@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.0.1.tgz" - integrity sha512-f14AnwFBy4/1A19zWthK+Ii80YDz+4qt8oPpK3julywXsheSxPBqgsND3LVBBvB2p3rJHvbo2m3HyB9Tco1JRw== - dependencies: - "@docusaurus/logger" "2.0.1" - "@docusaurus/utils" "2.0.1" - joi "^17.6.0" - js-yaml "^4.1.0" - tslib "^2.4.0" - "@docusaurus/utils-validation@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.4.0.tgz#1ed92bfab5da321c4a4d99cad28a15627091aa90" @@ -1737,27 +1585,6 @@ js-yaml "^4.1.0" tslib "^2.4.0" -"@docusaurus/utils@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.0.1.tgz" - integrity sha512-u2Vdl/eoVwMfUjDCkg7FjxoiwFs/XhVVtNxQEw8cvB+qaw6QWyT73m96VZzWtUb1fDOefHoZ+bZ0ObFeKk9lMQ== - dependencies: - "@docusaurus/logger" "2.0.1" - "@svgr/webpack" "^6.2.1" - file-loader "^6.2.0" - fs-extra "^10.1.0" - github-slugger "^1.4.0" - globby "^11.1.0" - gray-matter "^4.0.3" - js-yaml "^4.1.0" - lodash "^4.17.21" - micromatch "^4.0.5" - resolve-pathname "^3.0.0" - shelljs "^0.8.5" - tslib "^2.4.0" - url-loader "^4.1.1" - webpack "^5.73.0" - "@docusaurus/utils@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.4.0.tgz#fdf0c3545819e48bb57eafc5057495fd4d50e900" @@ -1862,7 +1689,7 @@ unist-builder "2.0.3" unist-util-visit "2.0.3" -"@mdx-js/react@^1.6.21", "@mdx-js/react@^1.6.22": +"@mdx-js/react@^1.6.22": version "1.6.22" resolved "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.22.tgz" integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg== @@ -3000,7 +2827,7 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" -clsx@^1.1.1, clsx@^1.2.1: +clsx@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== @@ -3752,11 +3579,6 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -eta@^1.12.3: - version "1.12.3" - resolved "https://registry.npmjs.org/eta/-/eta-1.12.3.tgz" - integrity sha512-qHixwbDLtekO/d51Yr4glcaUJCIjGVJyTzuqV4GPlgZo1YpgOKG+avQynErZIYrfM6JIJdtiG2Kox8tbb+DoGg== - eta@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/eta/-/eta-2.0.1.tgz#199e675359cb6e19d38f29e1f405e1ba0e79a6df" @@ -4544,10 +4366,10 @@ indent-string@^4.0.0: resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -infima@0.2.0-alpha.42: - version "0.2.0-alpha.42" - resolved "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.42.tgz" - integrity sha512-ift8OXNbQQwtbIt6z16KnSWP7uJ/SysSMFI4F87MNRTicypfl4Pv3E2OGVv6N3nSZFJvA8imYulCBS64iyHYww== +infima@0.2.0-alpha.43: + version "0.2.0-alpha.43" + resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.43.tgz#f7aa1d7b30b6c08afef441c726bac6150228cbe0" + integrity sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ== inflight@^1.0.4: version "1.0.6" @@ -6091,9 +5913,9 @@ react-dev-utils@^12.0.1: strip-ansi "^6.0.1" text-table "^0.2.0" -react-dom@^17.0.1: +react-dom@^17.0.2: version "17.0.2" - resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== dependencies: loose-envify "^1.1.0" @@ -6193,9 +6015,9 @@ react-textarea-autosize@^8.3.2: use-composed-ref "^1.3.0" use-latest "^1.2.1" -react@^17.0.1: +react@^17.0.2: version "17.0.2" - resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== dependencies: loose-envify "^1.1.0" @@ -7337,6 +7159,11 @@ use-latest@^1.2.1: dependencies: use-isomorphic-layout-effect "^1.1.1" +use-sync-external-store@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" diff --git a/frontend/assets/images/icons/app-icons/more-vertical.svg b/frontend/assets/images/icons/app-icons/more-vertical.svg new file mode 100644 index 0000000000..37acaa4623 --- /dev/null +++ b/frontend/assets/images/icons/app-icons/more-vertical.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/assets/images/no-apps.svg b/frontend/assets/images/no-apps.svg index dc9d656f0b..ebd604e9ac 100644 --- a/frontend/assets/images/no-apps.svg +++ b/frontend/assets/images/no-apps.svg @@ -1,4 +1,4 @@ - + diff --git a/frontend/assets/images/rocket.svg b/frontend/assets/images/rocket.svg index 4f0f67c38d..16fb357cec 100644 --- a/frontend/assets/images/rocket.svg +++ b/frontend/assets/images/rocket.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/frontend/assets/translations/en.json b/frontend/assets/translations/en.json index 44e528a4b1..7118f15f3d 100644 --- a/frontend/assets/translations/en.json +++ b/frontend/assets/translations/en.json @@ -3,6 +3,7 @@ "readDocumentation": "Read documentation", "cancel": "Cancel", "save": "Save", + "savechanges": "Save changes", "back": "Back", "edit": "Edit", "search": "Search", @@ -36,7 +37,8 @@ "path": "Path", "query": "Query", "requestBody": "Request Body", - "page": "Page" + "page": "Page", + "searchItem": "Search item" }, "errorBoundary": "Something went wrong.", "viewer": "Sorry!. This app is under maintenance", @@ -228,7 +230,7 @@ "view": "View" }, "organization": { - "addNewWorkSpace":"Add new workspace", + "addNewWorkSpace": "Add new workspace", "loadOrganizations": "Load Organizations", "createWorkspace": "Create workspace", "workspaceName": "workspace name", @@ -243,18 +245,21 @@ }, "manageUsers": { "usersAndPermission": "Users & Permissions", - "inviteNewUser": "Invite new user", + "inviteNewUser": "Invite one user", + "inviteUsers": "Invite users", "name": "NAME", "email": "EMAIL", "status": "STATUS", "archive": "Archive", "unarchive": "Unarchive", - "addNewUser": "Add new user", + "addNewUser": "Add users", "emailAddress": "Email address", "createUser": "Create User", "enterFirstName": "Enter First Name", "enterLastName": "Enter Last Name", - "enterEmail": "Enter Email" + "enterEmail": "Enter email id", + "enterFulltName": "Enter full name", + "inviteNewUsers":"Invite new users" }, "manageGroups": { "permissions": { @@ -275,7 +280,7 @@ "name": "name", "addUsersToGroup": "Select users to add to the group", "email": "email", - "resource": "resource", + "resource": "Resource", "createUpdateDelete": "Create/Update/Delete", "folder": "Folder" } @@ -292,7 +297,7 @@ "loginUrl": "Login URL", "workspaceLogin": "Use this URL to login directly to this workspace", "allowDefaultSso": "Allow default SSO", - "ssoAuth": "Allow users to authenticate via default SSO. Default SSO configurations can be overridden by workspace level SSO." + "ssoAuth": "Allow users to authenticate via default SSO. Default SSO configurations can be overridden by \n workspace level SSO." }, "google": { "title": "Google", @@ -355,7 +360,8 @@ "newPassword": "New password", "confirmNewPassword": "Confirm new password", "enterCurrentPassword": "Enter current password", - "enterNewPassword": "Enter new password" + "enterNewPassword": "Enter new password", + "inviteUsers": "Invite users" }, "profile": "Profile", "logout": "Logout" @@ -379,7 +385,7 @@ "blankPage": { "welcomeToToolJet": "Welcome to your new ToolJet workspace", "getStartedCreateNewApp": "You can get started by creating a new application or by creating an application using a template in ToolJet Library.", - "importApplication": "Import an application" + "importApplication": "Import an app" }, "foldersSection": { "allApplications": "All apps", @@ -394,7 +400,7 @@ "wishToDeleteFolder": "Are you sure you want to delete the folder? Apps within the folder will not be deleted." }, "header": { - "createNewApplication": "New app", + "createNewApplication": "Create new app", "import": "Import", "chooseFromTemplate": "Choose from template" }, diff --git a/frontend/ee/components/UsersPage/UsersFilter.jsx b/frontend/ee/components/UsersPage/UsersFilter.jsx index 441a456c02..588b740188 100644 --- a/frontend/ee/components/UsersPage/UsersFilter.jsx +++ b/frontend/ee/components/UsersPage/UsersFilter.jsx @@ -1,5 +1,6 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import Select from '@/_ui/Select'; +import { debounce } from 'lodash'; const userStatusOptions = [ { name: 'All', value: '' }, @@ -8,102 +9,81 @@ const userStatusOptions = [ { name: 'Archived', value: 'archived' }, ]; -const UsersFilter = ({ filterList, darkMode, clearIconPressed }) => { - const [options, setOptions] = React.useState({ email: '', firstName: '', lastName: '', status: '' }); +const UsersFilter = ({ filterList }) => { + const [options, setOptions] = useState({ searchText: '', status: '' }); + const [statusVal, setStatusVal] = useState(''); + const [queryVal, setQueryVal] = useState(); - const valuesChanged = (event, key) => { + const statusValuesChanged = (event) => { let newOptions = {}; - if (!key) { - newOptions = { ...options, [event.target.name]: event.target.value }; - } else { - newOptions = { ...options, [key]: event }; - } + newOptions = { + ...options, + searchText: queryVal, + status: event, + }; setOptions(newOptions); }; - const clearTextAndResult = () => { - setOptions({ email: '', firstName: '', lastName: '', status: '' }); - clearIconPressed(); + const queryValuesChanged = (event) => { + let newOptions = {}; + newOptions = { + ...options, + status: statusVal, + searchText: event.target.value, + }; + setOptions(newOptions); }; + useEffect(() => { + const debouncedFilter = debounce(() => { + filterList(options); + }, 500); - const handleEnterKey = (e) => { - if (e.key === 'Enter') filterList(options); - }; + debouncedFilter(); + return debouncedFilter.cancel; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [options.searchText, options.status]); return ( -
-
-
- +
+
+
+
Showing
+
+ -
-
- -
-
- { + setQueryVal(e.target.value); + queryValuesChanged(e); + }} + data-cy="input-field-user-filter-search" + />
+
); }; diff --git a/frontend/ee/components/UsersPage/UsersTable.jsx b/frontend/ee/components/UsersPage/UsersTable.jsx index e05252957a..d1c181d4c6 100644 --- a/frontend/ee/components/UsersPage/UsersTable.jsx +++ b/frontend/ee/components/UsersPage/UsersTable.jsx @@ -1,9 +1,11 @@ import React from 'react'; import { CopyToClipboard } from 'react-copy-to-clipboard'; -import Avatar from '../../../src/_ui/Avatar'; +import Avatar from '@/_ui/Avatar'; import Skeleton from 'react-loading-skeleton'; import cx from 'classnames'; import { Pagination } from '@/_components'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; import { Tooltip } from 'react-tooltip'; const UsersTable = ({ @@ -21,20 +23,22 @@ const UsersTable = ({ translator, }) => { return ( -
-
-
- +
+
+
+
- - + + {users && users[0]?.status ? ( - + ) : ( )} + + {isLoading ? ( @@ -70,74 +74,75 @@ const UsersTable = ({ users.length > 0 && users.map((user) => ( - {user.status && ( )} ))} diff --git a/frontend/package.json b/frontend/package.json index 0b5ae61597..4a57e6c18d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -181,10 +181,6 @@ "eslintConfig": { "extends": "react-app" }, - "browserslist": { - "production": [], - "development": [] - }, "jest": { "transform": { "^.+\\.js?$": "babel-jest", diff --git a/frontend/src/App/App.jsx b/frontend/src/App/App.jsx index 4dff8cf5d3..d3dd3f6580 100644 --- a/frontend/src/App/App.jsx +++ b/frontend/src/App/App.jsx @@ -33,6 +33,7 @@ import { VerificationSuccessInfoScreen } from '@/SuccessInfoScreen'; import '@/_styles/theme.scss'; import { AppLoader } from '@/AppLoader'; import SetupScreenSelfHost from '../SuccessInfoScreen/SetupScreenSelfHost'; +export const BreadCrumbContext = React.createContext({}); import 'react-tooltip/dist/react-tooltip.css'; const AppWrapper = (props) => { @@ -55,7 +56,9 @@ class AppComponent extends React.Component { darkMode: localStorage.getItem('darkMode') === 'true', }; } - + updateSidebarNAV = (val) => { + this.setState({ sidebarNav: val }); + }; fetchMetadata = () => { tooljetService.fetchMetaData().then((data) => { localStorage.setItem('currentVersion', data.installed_version); @@ -106,6 +109,7 @@ class AppComponent extends React.Component { } else if (isApplicationsPath) { this.updateCurrentSession({ authentication_failed: true, + load_app: true, }); } }); @@ -138,6 +142,7 @@ class AppComponent extends React.Component { ...data, current_organization_name, organizations: response.organizations, + load_app: true, }); // if user is trying to load the workspace login page, then redirect to the dashboard @@ -173,6 +178,7 @@ class AppComponent extends React.Component { this.updateCurrentSession({ current_organization_name, + load_app: true, }); if (!this.isThisWorkspaceLoginPage()) @@ -225,7 +231,8 @@ class AppComponent extends React.Component { }, }; } - + const { sidebarNav } = this.state; + const { updateSidebarNAV } = this; return ( <>
@@ -314,85 +321,91 @@ class AppComponent extends React.Component { } /> - - - - } - /> - - - - } - /> - - - - } - /> - {window.public_config?.ENABLE_TOOLJET_DB == 'true' && ( + + + - + } /> - )} - {window.public_config?.ENABLE_MARKETPLACE_FEATURE === 'true' && ( - - + + + } /> - )} - } /> - - - - } - /> - - - - } - /> - { - if (authenticationService?.currentSessionValue?.current_organization_id) { - return ; + + + } - return ; - }} - /> - + /> + {window.public_config?.ENABLE_TOOLJET_DB == 'true' && ( + + + + } + /> + )} + + {window.public_config?.ENABLE_MARKETPLACE_FEATURE === 'true' && ( + + + + } + /> + )} + } /> + + + + } + /> + + + + } + /> + { + if (authenticationService?.currentSessionValue?.current_organization_id) { + return ; + } + return ; + }} + /> + +
+ ); diff --git a/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx b/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx index 8669c2878b..05fcdc3f7b 100644 --- a/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx +++ b/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx @@ -63,6 +63,7 @@ export const CreateVersion = ({ setShowCreateAppVersion(false); }} title={t('editor.appVersionManager.createVersion', 'Create new version')} + checkForBackground={true} >
{ diff --git a/frontend/src/Editor/AppVersionsManager/EditVersionModal.jsx b/frontend/src/Editor/AppVersionsManager/EditVersionModal.jsx index 8710cf85bc..9a714bdfab 100644 --- a/frontend/src/Editor/AppVersionsManager/EditVersionModal.jsx +++ b/frontend/src/Editor/AppVersionsManager/EditVersionModal.jsx @@ -45,6 +45,7 @@ export const EditVersion = ({ show={showEditAppVersion} closeModal={() => setShowEditAppVersion(false)} title={t('editor.appVersionManager.editVersion', 'Edit Version')} + checkForBackground={true} > { diff --git a/frontend/src/Editor/AppVersionsManager/List.jsx b/frontend/src/Editor/AppVersionsManager/List.jsx index 2fe85dfebd..11dec16a0c 100644 --- a/frontend/src/Editor/AppVersionsManager/List.jsx +++ b/frontend/src/Editor/AppVersionsManager/List.jsx @@ -19,6 +19,7 @@ export const AppVersionsManager = function ({ versionName: '', showModal: false, }); + const darkMode = localStorage.getItem('darkMode') === 'true'; useEffect(() => { setGetAppVersionStatus('loading'); @@ -128,6 +129,7 @@ export const AppVersionsManager = function ({ value={editingVersion.id} onChange={(id) => selectVersion(id)} {...customSelectProps} + className={` ${darkMode && 'dark-theme'}`} /> ); diff --git a/frontend/src/Editor/Components/DropDown.jsx b/frontend/src/Editor/Components/DropDown.jsx index 0edee5d476..9743041ebf 100644 --- a/frontend/src/Editor/Components/DropDown.jsx +++ b/frontend/src/Editor/Components/DropDown.jsx @@ -130,6 +130,7 @@ export const DropDown = function DropDown({ input: (provided, _state) => ({ ...provided, color: darkMode ? 'white' : 'black', + margin: '0px', }), indicatorSeparator: (_state) => ({ display: 'none', diff --git a/frontend/src/Editor/Components/Html.jsx b/frontend/src/Editor/Components/Html.jsx index d192ec1f5b..fa803e74f3 100644 --- a/frontend/src/Editor/Components/Html.jsx +++ b/frontend/src/Editor/Components/Html.jsx @@ -13,6 +13,13 @@ export const Html = function ({ height, properties, styles, darkMode, dataCy }) useEffect(() => { setRawHtml(stringifyHTML); }, [stringifyHTML]); + DOMPurify.addHook('afterSanitizeAttributes', function (node) { + // set all elements owning target to target=_blank + if ('target' in node) { + node.setAttribute('target', '_blank'); + node.setAttribute('rel', 'noopener'); + } + }); return (
diff --git a/frontend/src/Editor/Components/Modal.jsx b/frontend/src/Editor/Components/Modal.jsx index 7921161d94..c745f11fc7 100644 --- a/frontend/src/Editor/Components/Modal.jsx +++ b/frontend/src/Editor/Components/Modal.jsx @@ -20,7 +20,15 @@ export const Modal = function Modal({ dataCy, }) { const [showModal, setShowModal] = useState(false); - const { hideOnEsc, hideCloseButton, hideTitleBar, loadingState, useDefaultButton, triggerButtonLabel } = properties; + const { + closeOnClickingOutside = false, + hideOnEsc, + hideCloseButton, + hideTitleBar, + loadingState, + useDefaultButton, + triggerButtonLabel, + } = properties; const { headerBackgroundColor, headerTextColor, @@ -87,7 +95,7 @@ export const Modal = function Modal({ }; useEffect(() => { - if (containerProps.mode === 'view') { + if (closeOnClickingOutside) { const handleClickOutside = (event) => { const modalRef = parentRef.current.parentElement.parentElement.parentElement; @@ -102,7 +110,7 @@ export const Modal = function Modal({ }; } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [containerProps.mode, parentRef]); + }, [closeOnClickingOutside, parentRef]); return (
@@ -116,6 +124,7 @@ export const Modal = function Modal({ setShowModal(true); setExposedVariable('show', true).then(() => fireEvent('onOpen')); }} + data-cy={`${dataCy}-launch-button`} > {triggerButtonLabel ?? 'Show Modal'} @@ -196,11 +205,14 @@ const Component = ({ children, ...restProps }) => { /> )} {!hideTitleBar && ( - - {title} + + + {title} + {!hideCloseButton && ( { e.preventDefault(); @@ -228,7 +240,7 @@ const Component = ({ children, ...restProps }) => { )} )} - + {children} diff --git a/frontend/src/Editor/Components/RangeSlider.jsx b/frontend/src/Editor/Components/RangeSlider.jsx index c19057a24c..cd3dff473f 100644 --- a/frontend/src/Editor/Components/RangeSlider.jsx +++ b/frontend/src/Editor/Components/RangeSlider.jsx @@ -36,7 +36,7 @@ export const RangeSlider = function RangeSlider({ height, properties, styles, se useEffect(() => { setExposedVariable('value', enableTwoHandle ? twoHandlesArray : singleHandleValue); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sliderRef.current, enableTwoHandle]); + }, [enableTwoHandle]); const onSliderChange = (value) => { setExposedVariable('value', value); diff --git a/frontend/src/Editor/Components/Table/AddNewRowComponent.jsx b/frontend/src/Editor/Components/Table/AddNewRowComponent.jsx new file mode 100644 index 0000000000..1f9f662415 --- /dev/null +++ b/frontend/src/Editor/Components/Table/AddNewRowComponent.jsx @@ -0,0 +1,169 @@ +import React, { useEffect, useState } from 'react'; +import { useTable, useBlockLayout } from 'react-table'; +import _ from 'lodash'; +import { Tooltip } from 'react-tooltip'; + +export function AddNewRowComponent({ + hideAddNewRowPopup, + tableType, + darkMode, + mergeToAddNewRowsDetails, + onEvent, + component, + setExposedVariable, + allColumns, + defaultColumn, + columns, + addNewRowsDetails, +}) { + const getNewRowObject = () => { + return allColumns.reduce((accumulator, column) => { + const key = column.key ?? column.exportValue; + accumulator[key] = ''; + return accumulator; + }, {}); + }; + const newRow = getNewRowObject(); + const { newRowsChangeSet } = addNewRowsDetails; + const rowsFromPrevOperationPresent = _.isEmpty(addNewRowsDetails.newRowsDataUpdates) ? false : true; + const previousRowsData = rowsFromPrevOperationPresent + ? Object.keys(addNewRowsDetails.newRowsDataUpdates).reduce((accumulator, row) => { + accumulator[row] = addNewRowsDetails.newRowsDataUpdates[row]; + return accumulator; + }, []) + : null; + const [newRowsState, setNewRowsState] = useState(rowsFromPrevOperationPresent ? previousRowsData : [newRow]); + const newRowData = useTable( + { + columns, + data: newRowsState, + defaultColumn, + }, + useBlockLayout + ); + useEffect(() => { + if (!rowsFromPrevOperationPresent) { + const newRowDataUpdates = newRowsState.reduce((accumulator, row, index) => { + accumulator[index] = newRowsState[index]; + return accumulator; + }, {}); + setExposedVariable('newRows', newRowsState).then(() => { + mergeToAddNewRowsDetails({ newRowsDataUpdates: newRowDataUpdates }); + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = newRowData; + + return ( +
+
+
+

+ Add new rows +

+
+
+ +
+
+
+
{translator('header.organization.menus.manageUsers.name', 'Name')}{translator('header.organization.menus.manageUsers.email', 'Email')}{translator('header.organization.menus.manageUsers.name', 'Name')}{translator('header.organization.menus.manageUsers.email', 'Email')}{translator('header.organization.menus.manageUsers.status', 'Status')}{translator('header.organization.menus.manageUsers.status', 'Status')}
+ - + {user.name} - + {user.email} - + {user.status} {user.status === 'invited' && 'invitation_token' in user ? ( - <> +
- + + +

Copy link

+
- +
) : ( '' )}
- +
+ + {headerGroups.map((headerGroup, index) => { + return ( + + {headerGroup.headers.map((column, index) => { + return ( + + ); + })} + + ); + })} + + + {rows.map((row, index) => { + prepareRow(row); + return ( + + {row.cells.map((cell, index) => { + let cellProps = cell.getCellProps(); + const isEditable = true; + return ( + + ); + })} + + ); + })} + +
+
{column.render('Header')}
+
+
+ {cell.render('Cell', { cell, isEditable, newRowsChangeSet })} +
+
+ + +
+
+ + +
+
+ ); +} diff --git a/frontend/src/Editor/Components/Table/Table.jsx b/frontend/src/Editor/Components/Table/Table.jsx index 25d5a9e023..0ebd4f62ea 100644 --- a/frontend/src/Editor/Components/Table/Table.jsx +++ b/frontend/src/Editor/Components/Table/Table.jsx @@ -45,6 +45,7 @@ import GenerateEachCellValue from './GenerateEachCellValue'; // eslint-disable-next-line import/no-unresolved import { toast } from 'react-hot-toast'; import { Tooltip } from 'react-tooltip'; +import { AddNewRowComponent } from './AddNewRowComponent'; export function Table({ id, @@ -125,14 +126,20 @@ export function Table({ const [generatedColumn, setGeneratedColumn] = useState([]); const mergeToTableDetails = (payload) => dispatch(reducerActions.mergeToTableDetails(payload)); const mergeToFilterDetails = (payload) => dispatch(reducerActions.mergeToFilterDetails(payload)); + const mergeToAddNewRowsDetails = (payload) => dispatch(reducerActions.mergeToAddNewRowsDetails(payload)); const mounted = useMounted(); + const prevDataFromProps = useRef(); + useEffect(() => { + if (mounted) prevDataFromProps.current = properties.data; + }, [JSON.stringify(properties.data)]); + useEffect(() => { setExposedVariable( 'filters', tableDetails.filterDetails.filters.map((filter) => filter.value) ); - }, [JSON.stringify(tableDetails.filterDetails.filters)]); + }, [JSON.stringify(tableDetails?.filterDetails?.filters)]); useEffect( () => mergeToTableDetails({ columnProperties: component?.definition?.properties?.columns?.value }), @@ -156,6 +163,14 @@ export function Table({ mergeToFilterDetails({ filtersVisible: false }); } + function showAddNewRowPopup() { + mergeToAddNewRowsDetails({ addingNewRows: true }); + } + + function hideAddNewRowPopup() { + mergeToAddNewRowsDetails({ addingNewRows: false }); + } + const defaultColumn = React.useMemo( () => ({ minWidth: 60, @@ -164,7 +179,7 @@ export function Table({ [] ); - function handleCellValueChange(index, key, value, rowData) { + function handleExistingRowCellValueChange(index, key, value, rowData) { const changeSet = tableDetails.changeSet; const dataUpdates = tableDetails.dataUpdates || []; const clonedTableData = _.cloneDeep(tableData); @@ -198,6 +213,37 @@ export function Table({ return setExposedVariables({ ...changesToBeSavedAndExposed, updatedData: clonedTableData }); } + const copyOfTableDetails = useRef(tableDetails); + useEffect(() => { + copyOfTableDetails.current = _.cloneDeep(tableDetails); + }, [JSON.stringify(tableDetails)]); + + function handleNewRowCellValueChange(index, key, value, rowData) { + const changeSet = copyOfTableDetails.current.addNewRowsDetails.newRowsChangeSet || {}; + const dataUpdates = copyOfTableDetails.current.addNewRowsDetails.newRowsDataUpdates || {}; + let obj = changeSet ? changeSet[index] || {} : {}; + obj = _.set(obj, key, value); + let newChangeset = { + ...changeSet, + [index]: { + ...obj, + }, + }; + obj = _.set({ ...rowData, ...obj }, key, value); + + let newDataUpdates = { + ...dataUpdates, + [index]: { ...obj }, + }; + const changesToBeSaved = { newRowsDataUpdates: newDataUpdates, newRowsChangeSet: newChangeset }; + const changesToBeExposed = Object.keys(newDataUpdates).reduce((accumulator, row) => { + accumulator.push({ ...newDataUpdates[row] }); + return accumulator; + }, []); + mergeToAddNewRowsDetails(changesToBeSaved); + return setExposedVariables({ newRows: changesToBeExposed }); + } + function getExportFileBlob({ columns, fileType, fileName }) { let headers = columns.map((column) => { return { exportValue: String(column.exportValue), key: column.key ? String(column.key) : column.key }; @@ -304,7 +350,7 @@ export function Table({ columnProperties: useDynamicColumn ? generatedColumn : component.definition.properties.columns.value, columnSizes, currentState, - handleCellValueChange, + handleCellValueChange: handleExistingRowCellValueChange, customFilter, defaultColumn, changeSet: tableDetails.changeSet, @@ -318,6 +364,24 @@ export function Table({ darkMode, }); + const columnDataForAddNewRows = generateColumnsData({ + columnProperties: useDynamicColumn ? generatedColumn : component.definition.properties.columns.value, + columnSizes, + currentState, + handleCellValueChange: handleNewRowCellValueChange, + customFilter, + defaultColumn, + changeSet: tableDetails.addNewRowsDetails.newRowsChangeSet, + tableData, + variablesExposedForPreview, + exposeToCodeHinter, + id, + fireEvent, + tableRef, + t, + darkMode, + }); + const [leftActionsCellData, rightActionsCellData] = useMemo( () => generateActionsData({ @@ -339,7 +403,9 @@ export function Table({ const optionsData = columnData.map((column) => column.columnOptions?.selectOptions); const columns = useMemo( - () => [...leftActionsCellData, ...columnData, ...rightActionsCellData], + () => { + return [...leftActionsCellData, ...columnData, ...rightActionsCellData]; + }, [ JSON.stringify(columnData), JSON.stringify(tableData), @@ -355,15 +421,29 @@ export function Table({ ] // Hack: need to fix ); - const data = useMemo( - () => tableData, - [ - tableData.length, - tableDetails.changeSet, - component.definition.properties.data.value, - JSON.stringify(properties.data), - ] - ); + const columnsForAddNewRow = useMemo(() => { + return [...columnDataForAddNewRows]; + }, [JSON.stringify(columnDataForAddNewRows), darkMode, tableDetails.addNewRowsDetails.addingNewRows]); + + const data = useMemo(() => { + if (!_.isEqual(properties.data, prevDataFromProps.current)) { + if ( + !_.isEmpty(exposedVariables.newRows) || + !_.isEmpty(tableDetails.addNewRowsDetails.newRowsDataUpdates) || + tableDetails.addNewRowsDetails.addingNewRows + ) { + setExposedVariable('newRows', []).then(() => { + mergeToAddNewRowsDetails({ newRowsDataUpdates: {}, newRowsChangeSet: {}, addingNewRows: false }); + }); + } + } + return tableData; + }, [ + tableData.length, + tableDetails.changeSet, + component.definition.properties.data.value, + JSON.stringify(properties.data), + ]); useEffect(() => { if ( @@ -463,6 +543,7 @@ export function Table({ ]); } ); + const currentColOrder = React.useRef(); const sortOptions = useMemo(() => { @@ -537,6 +618,27 @@ export function Table({ }, [JSON.stringify(tableData), JSON.stringify(tableDetails.changeSet)] ); + registerAction( + 'discardNewlyAddedRows', + async function () { + if ( + tableDetails.addNewRowsDetails.addingNewRows && + (Object.keys(tableDetails.addNewRowsDetails.newRowsChangeSet || {}).length > 0 || + Object.keys(tableDetails.addNewRowsDetails.newRowsDataUpdates || {}).length > 0) + ) { + setExposedVariables({ + newRows: [], + }).then(() => { + mergeToAddNewRowsDetails({ newRowsChangeSet: {}, newRowsDataUpdates: {}, addingNewRows: false }); + }); + } + }, + [ + JSON.stringify(tableDetails.addNewRowsDetails.newRowsChangeSet), + tableDetails.addNewRowsDetails.addingNewRows, + JSON.stringify(tableDetails.addNewRowsDetails.newRowsDataUpdates), + ] + ); useEffect(() => { const selectedRowsOriginalData = selectedFlatRows.map((row) => row.original); @@ -656,7 +758,7 @@ export function Table({ > {/* Show top bar unless search box is disabled and server pagination is enabled */} {(displaySearchBox || showDownloadButton || showFilterButton) && ( -
+
)}
+ + {showFilterButton && ( <> @@ -1047,6 +1167,21 @@ export function Table({ fireEvent={fireEvent} /> )} + {tableDetails.addNewRowsDetails.addingNewRows && ( + + )} ); } diff --git a/frontend/src/Editor/Components/Table/columns/index.jsx b/frontend/src/Editor/Components/Table/columns/index.jsx index 829ba33030..3e1853831c 100644 --- a/frontend/src/Editor/Components/Table/columns/index.jsx +++ b/frontend/src/Editor/Components/Table/columns/index.jsx @@ -7,6 +7,7 @@ import { Tags } from '../Tags'; import { Radio } from '../Radio'; import { Toggle } from '../Toggle'; import { Datepicker } from '../Datepicker'; +import moment from 'moment'; export default function generateColumnsData({ columnProperties, @@ -28,6 +29,7 @@ export default function generateColumnsData({ return columnProperties.map((column) => { const columnSize = columnSizes[column.id] || columnSizes[column.name]; const columnType = column.columnType; + let sortType = 'alphanumeric'; const columnOptions = {}; if ( @@ -52,6 +54,21 @@ export default function generateColumnsData({ column.isTimeChecked = column.isTimeChecked ? column.isTimeChecked : false; column.dateFormat = column.dateFormat ? column.dateFormat : 'DD/MM/YYYY'; column.parseDateFormat = column.parseDateFormat ?? column.dateFormat; //backwards compatibility + sortType = (firstDate, secondDate) => { + // Return -1 if second date is higher, 1 if first date is higher + if (secondDate?.original[column.name] === '') { + return 1; + } else if (firstDate?.original[column.name] === '') return -1; + + const parsedFirstDate = moment(firstDate?.original[column.name], column.parseDateFormat); + const parsedSecondDate = moment(secondDate?.original[column.name], column.parseDateFormat); + + if (moment(parsedSecondDate).isSameOrAfter(parsedFirstDate)) { + return -1; + } else { + return 1; + } + }; } const width = columnSize || defaultColumn.width; @@ -73,8 +90,9 @@ export default function generateColumnsData({ maxLength: column.maxLength, regex: column.regex, customRule: column?.customRule, - Cell: function ({ cell, isEditable }) { - const rowChangeSet = changeSet ? changeSet[cell.row.index] : null; + Cell: function ({ cell, isEditable, newRowsChangeSet = null }) { + const updatedChangeSet = newRowsChangeSet === null ? changeSet : newRowsChangeSet; + const rowChangeSet = updatedChangeSet ? updatedChangeSet[cell.row.index] : null; let cellValue = rowChangeSet ? rowChangeSet[column.key || column.name] ?? cell.value : cell.value; const rowData = tableData[cell.row.index]; @@ -129,7 +147,7 @@ export default function generateColumnsData({
{ if (e.key === 'Enter') { if (e.target.defaultValue !== e.target.value) { @@ -196,7 +214,7 @@ export default function generateColumnsData({
{ if (e.key === 'Enter') { if (e.target.defaultValue !== e.target.value) { @@ -240,7 +258,7 @@ export default function generateColumnsData({ darkMode ? 'text-light textarea-dark-theme' : 'text-muted' }`} readOnly={!isEditable} - style={{ maxWidth: width, minWidth: width - 10 }} + style={{ maxWidth: width }} onBlur={(e) => { if (isEditable) { handleCellValueChange(cell.row.index, column.key || column.name, e.target.value, cell.row.original); diff --git a/frontend/src/Editor/Components/Table/reducer.js b/frontend/src/Editor/Components/Table/reducer.js index 4630fe25c0..479d737674 100644 --- a/frontend/src/Editor/Components/Table/reducer.js +++ b/frontend/src/Editor/Components/Table/reducer.js @@ -5,6 +5,11 @@ export const initialState = () => ({ filters: [], filtersVisible: false, }, + addNewRowsDetails: { + addingNewRows: false, + newRowsDataUpdates: {}, + newRowsChangeSet: {}, + }, }); const mergeToTableDetails = (payload) => ({ @@ -17,9 +22,15 @@ const mergeToFilterDetails = (payload) => ({ payload, }); +const mergeToAddNewRowsDetails = (payload) => ({ + type: 'MERGE_TO_ADD_NEW_ROWS', + payload, +}); + export const reducerActions = { mergeToFilterDetails, mergeToTableDetails, + mergeToAddNewRowsDetails, }; export const reducer = (state, action) => { @@ -38,7 +49,14 @@ export const reducer = (state, action) => { ...action.payload, }, }; - + case 'MERGE_TO_ADD_NEW_ROWS': + return { + ...state, + addNewRowsDetails: { + ...state.addNewRowsDetails, + ...action.payload, + }, + }; default: return initialState(); } diff --git a/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx b/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx index 1fbfa1d61b..b5ed4b5fb9 100644 --- a/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx +++ b/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx @@ -19,6 +19,8 @@ import { capitalize, isEmpty } from 'lodash'; import { Card } from '@/_ui/Card'; import { withTranslation, useTranslation } from 'react-i18next'; import { camelizeKeys, decamelizeKeys } from 'humps'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; class DataSourceManagerComponent extends React.Component { constructor(props) { @@ -75,13 +77,13 @@ class DataSourceManagerComponent extends React.Component { componentDidUpdate(prevProps) { if (prevProps.selectedDataSource !== this.props.selectedDataSource) { let dataSourceMeta = this.getDataSourceMeta(this.props.selectedDataSource); - this.setState({ selectedDataSource: this.props.selectedDataSource, options: this.props.selectedDataSource?.options, dataSourceMeta, dataSourceSchema: this.props.selectedDataSource?.plugin?.manifestFile?.data, selectedDataSourceIcon: this.props.selectedDataSource?.plugin?.iconFile?.data, + connectionTestError: null, }); } } @@ -350,6 +352,7 @@ class DataSourceManagerComponent extends React.Component { queryString={this.state.queryString} activeDatasourceList={this.state.activeDatasourceList} scope={this.state.scope} + className="tj-text" />
{datasources.map((datasource) => ( @@ -488,7 +491,7 @@ class DataSourceManagerComponent extends React.Component { return ( <> -
+

{type}

{filteredDatasources.map((item) => ( - - - + {!this.props.isEditing && ( + + + + )}
)} {!selectedDataSource && ( - + {this.props.t('editor.queryManager.dataSourceManager.addNewDataSource', 'Add new datasource')} )} - this.hideModal()} - > - - + {!this.props.isEditing && ( + this.hideModal()} + > + + + )} {this.renderEnvironmentsTab(selectedDataSource)} @@ -722,22 +729,10 @@ class DataSourceManagerComponent extends React.Component {
- - - +
-

+

{this.props.t( 'editor.queryManager.dataSourceManager.whiteListIP', 'Please white-list our IP address if the data source is not publicly accessible.' @@ -758,25 +753,16 @@ class DataSourceManagerComponent extends React.Component { this.setState({ isCopied: true }); }} > - + )}

@@ -795,17 +781,16 @@ class DataSourceManagerComponent extends React.Component { )}
- +
)} @@ -832,22 +820,29 @@ class DataSourceManagerComponent extends React.Component { {!dataSourceMeta?.hideSave && selectedDataSource && dataSourceMeta.customTesting && (
- +
)} diff --git a/frontend/src/Editor/DataSourceManager/TestConnection.jsx b/frontend/src/Editor/DataSourceManager/TestConnection.jsx index 811e5a394c..2493550f44 100644 --- a/frontend/src/Editor/DataSourceManager/TestConnection.jsx +++ b/frontend/src/Editor/DataSourceManager/TestConnection.jsx @@ -2,8 +2,9 @@ import React, { useEffect, useState } from 'react'; import { toast } from 'react-hot-toast'; import { datasourceService } from '@/_services'; import { useTranslation } from 'react-i18next'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; -export const TestConnection = ({ kind, options, pluginId, onConnectionTestFailed, darkMode }) => { +export const TestConnection = ({ kind, options, pluginId, onConnectionTestFailed }) => { const [isTesting, setTestingStatus] = useState(false); const [connectionStatus, setConnectionStatus] = useState('unknown'); const [buttonText, setButtonText] = useState('Test Connection'); @@ -59,14 +60,15 @@ export const TestConnection = ({ kind, options, pluginId, onConnectionTestFailed )} {connectionStatus === 'unknown' && ( - + )}
); diff --git a/frontend/src/Editor/DraggableBox.jsx b/frontend/src/Editor/DraggableBox.jsx index f5f58b02b8..2e3f07cd80 100644 --- a/frontend/src/Editor/DraggableBox.jsx +++ b/frontend/src/Editor/DraggableBox.jsx @@ -176,7 +176,6 @@ export const DraggableBox = function DraggableBox({ }; const layoutData = inCanvas ? layouts[currentLayout] || defaultData : defaultData; - const gridWidth = canvasWidth / 43; const width = (canvasWidth * layoutData.width) / 43; diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index 63319c659d..84c9636589 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -183,12 +183,7 @@ class EditorComponent extends React.Component { ...this.state.currentState, globals: { ...this.state.currentState.globals, - userVars: { - email: currentUser.email, - firstName: currentUser.first_name, - lastName: currentUser.last_name, - groups: currentSession.group_permissions?.map((group) => group.group) || [], - }, + currentUser: userVars, }, }, userVars, @@ -983,6 +978,7 @@ class EditorComponent extends React.Component { return this.setState({ draftQuery: { ...this.state.draftQuery, name: newName }, renameQueryName: false, + selectedQuery: { ...this.state.selectedQuery, name: newName }, }); } dataqueryService diff --git a/frontend/src/Editor/Inspector/Components/Icon.jsx b/frontend/src/Editor/Inspector/Components/Icon.jsx index c9ade23625..b99468277d 100644 --- a/frontend/src/Editor/Inspector/Components/Icon.jsx +++ b/frontend/src/Editor/Inspector/Components/Icon.jsx @@ -45,7 +45,7 @@ export function Icon({ componentMeta, darkMode, ...restProps }) { diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/CreateRow.jsx b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/CreateRow.jsx index b8ba931291..c33a49792a 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/CreateRow.jsx +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/CreateRow.jsx @@ -88,6 +88,7 @@ export const CreateRow = React.memo(({ currentState, optionchanged, options, dar value={column} options={displayColumns} onChange={handleColumnChange} + customWrap={true} />
diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DeleteRows.jsx b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DeleteRows.jsx index 92d219d748..6078539ec0 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DeleteRows.jsx +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DeleteRows.jsx @@ -44,18 +44,11 @@ export const DeleteRows = React.memo(({ currentState, darkMode }) => { } const RenderFilterFields = ({ column, operator, value, id }) => { - const existingColumnOptions = Object.values(deleteRowsOptions?.where_filters).map((f) => f.column); let displayColumns = columns.map(({ accessor }) => ({ value: accessor, label: accessor, })); - if (existingColumnOptions.length > 0) { - displayColumns = displayColumns.filter( - ({ value }) => !existingColumnOptions.map((item) => item !== column && item).includes(value) - ); - } - const handleColumnChange = (selectedOption) => { updateFilterOptionsChanged({ ...deleteRowsOptions?.where_filters[id], ...{ column: selectedOption } }); }; diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/ListRows.jsx b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/ListRows.jsx index 5baac5e090..bdf8aa51aa 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/ListRows.jsx +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/ListRows.jsx @@ -77,19 +77,11 @@ export const ListRows = React.memo(({ currentState, darkMode }) => { } const RenderFilterFields = ({ column, operator, value, id }) => { - const existingColumnOptions = Object.values(listRowsOptions?.where_filters).map((f) => f.column); - let displayColumns = columns.map(({ accessor }) => ({ value: accessor, label: accessor, })); - if (existingColumnOptions.length > 0) { - displayColumns = displayColumns.filter( - ({ value }) => !existingColumnOptions.map((item) => item !== column && item).includes(value) - ); - } - const handleColumnChange = (selectedOption) => { updateFilterOptionsChanged({ ...listRowsOptions?.where_filters[id], ...{ column: selectedOption } }); }; diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/UpdateRows.jsx b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/UpdateRows.jsx index 3073af0c45..dccbae1e4e 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/UpdateRows.jsx +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/UpdateRows.jsx @@ -70,18 +70,11 @@ export const UpdateRows = React.memo(({ currentState, darkMode }) => { } const RenderFilterFields = ({ column, operator, value, id }) => { - const existingColumnOptions = Object.values(updateRowsOptions?.where_filters).map((f) => f.column); let displayColumns = columns.map(({ accessor }) => ({ value: accessor, label: accessor, })); - if (existingColumnOptions.length > 0) { - displayColumns = displayColumns.filter( - ({ value }) => !existingColumnOptions.map((item) => item !== column && item).includes(value) - ); - } - const handleColumnChange = (selectedOption) => { updateFilterOptionsChanged({ ...updateRowsOptions?.where_filters[id], ...{ column: selectedOption } }); }; diff --git a/frontend/src/Editor/QueryManager/QueryManager.jsx b/frontend/src/Editor/QueryManager/QueryManager.jsx index 442845d23e..7e35e28e91 100644 --- a/frontend/src/Editor/QueryManager/QueryManager.jsx +++ b/frontend/src/Editor/QueryManager/QueryManager.jsx @@ -167,6 +167,14 @@ class QueryManagerComponent extends React.Component { ); }; + componentDidUpdate(prevState) { + if (prevState?.selectedQuery?.name !== this.state?.selectedQuery?.name) { + this.setState({ + queryName: this.state.selectedQuery?.name, + }); + } + } + componentWillReceiveProps(nextProps) { if (nextProps.loadingDataSources) return; if (this.props.showQueryConfirmation && !nextProps.showQueryConfirmation) { diff --git a/frontend/src/Editor/Viewer.jsx b/frontend/src/Editor/Viewer.jsx index 5eabbb1fe1..676f6be3ab 100644 --- a/frontend/src/Editor/Viewer.jsx +++ b/frontend/src/Editor/Viewer.jsx @@ -157,7 +157,7 @@ class ViewerComponent extends React.Component { queries: queryState, components: {}, globals: { - currentUser: userVars, + currentUser: userVars, // currentUser is updated in setupViewer function as well theme: { name: this.props.darkMode ? 'dark' : 'light' }, urlparams: JSON.parse(JSON.stringify(queryString.parse(this.props.location.search))), }, @@ -302,38 +302,35 @@ class ViewerComponent extends React.Component { const versionId = this.props.params.versionId; this.subscription = authenticationService.currentSession.subscribe((currentSession) => { - if (currentSession?.group_permissions) { - const currentUser = currentSession.current_user; - const userVars = { - email: currentUser.email, - firstName: currentUser.first_name, - lastName: currentUser.last_name, - groups: currentSession?.group_permissions?.map((group) => group.group), - }; + if (currentSession?.load_app) { + if (currentSession?.group_permissions) { + const currentUser = currentSession.current_user; + const userVars = { + email: currentUser.email, + firstName: currentUser.first_name, + lastName: currentUser.last_name, + groups: currentSession?.group_permissions?.map((group) => group.group), + }; - this.setState({ - currentUser, - currentState: { - ...this.state.currentState, - globals: { - ...this.state.currentState.globals, - userVars: { - email: currentUser.email, - firstName: currentUser.first_name, - lastName: currentUser.last_name, - groups: currentSession?.group_permissions?.map((group) => group.group) || [], + this.setState({ + currentUser, + currentState: { + ...this.state.currentState, + globals: { + ...this.state.currentState.globals, + currentUser: userVars, // currentUser is updated in setStateForContainer function as well }, }, - }, - userVars, - }); - slug ? this.loadApplicationBySlug(slug) : this.loadApplicationByVersion(appId, versionId); - } else if (currentSession?.authentication_failed && !slug) { - const loginPath = (window.public_config?.SUB_PATH || '/') + 'login'; - const pathname = getSubpath() ? window.location.pathname.replace(getSubpath(), '') : window.location.pathname; - window.location.href = loginPath + `?redirectTo=${excludeWorkspaceIdFromURL(pathname)}`; - } else { - slug && this.loadApplicationBySlug(slug); + userVars, + }); + slug ? this.loadApplicationBySlug(slug) : this.loadApplicationByVersion(appId, versionId); + } else if (currentSession?.authentication_failed && !slug) { + const loginPath = (window.public_config?.SUB_PATH || '/') + 'login'; + const pathname = getSubpath() ? window.location.pathname.replace(getSubpath(), '') : window.location.pathname; + window.location.href = loginPath + `?redirectTo=${excludeWorkspaceIdFromURL(pathname)}`; + } else { + slug && this.loadApplicationBySlug(slug); + } } this.setState({ isLoading: false }); }); diff --git a/frontend/src/Editor/WidgetManager/widgetConfig.js b/frontend/src/Editor/WidgetManager/widgetConfig.js index accec8c3c0..9a1d5e9120 100644 --- a/frontend/src/Editor/WidgetManager/widgetConfig.js +++ b/frontend/src/Editor/WidgetManager/widgetConfig.js @@ -285,7 +285,7 @@ export const widgets = [ }, defaultSize: { width: 20, - height: 300, + height: 358, }, events: { onRowHovered: { displayName: 'Row hovered' }, @@ -297,6 +297,7 @@ export const widgets = [ onSort: { displayName: 'Sort applied' }, onCellValueChanged: { displayName: 'Cell value changed' }, onFilterChanged: { displayName: 'Filter changed' }, + onNewRowsAdded: { displayName: 'Add new rows' }, }, styles: { textColor: { @@ -397,6 +398,10 @@ export const widgets = [ handle: 'discardChanges', displayName: 'Discard Changes', }, + { + handle: 'discardNewlyAddedRows', + displayName: 'Discard newly added rows', + }, ], definition: { others: { @@ -833,7 +838,8 @@ export const widgets = [ }, hideTitleBar: { type: 'toggle', displayName: 'Hide title bar' }, hideCloseButton: { type: 'toggle', displayName: 'Hide close button' }, - hideOnEsc: { type: 'toggle', displayName: 'Hide on escape' }, + hideOnEsc: { type: 'toggle', displayName: 'Close on escape key' }, + closeOnClickingOutside: { type: 'toggle', displayName: 'Close on clicking outside' }, size: { type: 'select', @@ -933,6 +939,7 @@ export const widgets = [ hideTitleBar: { value: '{{false}}' }, hideCloseButton: { value: '{{false}}' }, hideOnEsc: { value: '{{true}}' }, + closeOnClickingOutside: { value: '{{false}}' }, }, events: [], styles: { diff --git a/frontend/src/GlobalDatasources/CreateDataSourceModal/index.jsx b/frontend/src/GlobalDatasources/CreateDataSourceModal/index.jsx index 83b8bb9fec..1a3d68c520 100644 --- a/frontend/src/GlobalDatasources/CreateDataSourceModal/index.jsx +++ b/frontend/src/GlobalDatasources/CreateDataSourceModal/index.jsx @@ -7,7 +7,7 @@ export const CreateDataSourceModal = () => { return (
-
+
)} -
+
-
- -
+
- +
); diff --git a/frontend/src/HomePage/AppList.jsx b/frontend/src/HomePage/AppList.jsx index 58641cb4c2..0ff33cc6b0 100644 --- a/frontend/src/HomePage/AppList.jsx +++ b/frontend/src/HomePage/AppList.jsx @@ -27,10 +27,10 @@ const AppList = (props) => { )} {!props.isLoading && props.meta.total_count > 0 && (
-
+
{props.apps.map((app) => { return ( -
+
{ - document.body.click(); - }; const { t } = useTranslation(); const Field = ({ text, onClick, customClass }) => { + const closeMenu = () => { + document.body.click(); + onClick(); + }; return (
{ closeMenu(); - onClick(); }} data-cy={`${text.toLowerCase().replace(/\s+/g, '-')}-card-option`} > @@ -44,48 +43,47 @@ export const AppMenu = function AppMenu({ rootClose onToggle={onMenuOpen} overlay={ - - -
- {canUpdateApp && ( - openAppActionModal('change-icon')} - /> - )} - {canCreateApp && ( - <> +
+ + +
+ {canUpdateApp && ( openAppActionModal('add-to-folder')} + text={t('homePage.appCard.changeIcon', 'Change Icon')} + onClick={() => openAppActionModal('change-icon')} /> - - {currentFolder.id && ( + )} + {canCreateApp && ( + <> openAppActionModal('remove-app-from-folder')} + text={t('homePage.appCard.addToFolder', 'Add to folder')} + onClick={() => openAppActionModal('add-to-folder')} /> - )} - - - - )} - {canDeleteApp && ( - - )} -
-
-
+ + {currentFolder.id && ( + openAppActionModal('remove-app-from-folder')} + /> + )} + + + + )} + {canDeleteApp && ( + + )} +
+ + +
} > -
+
-
+
-

+

{t('blankPage.welcomeToToolJet', 'Welcome to your new ToolJet workspace')}

-

+

{t( 'blankPage.getStartedCreateNewApp', 'You can get started by creating a new application or by creating an application using a template in ToolJet Library.' )}

-
+
@@ -142,19 +114,19 @@ export const BlankPage = function BlankPage({
{staticTemplates.map(({ id, name }) => { return ( -
deployApp(id)}> +
deployApp(id)}>

{name} @@ -167,7 +139,7 @@ export const BlankPage = function BlankPage({

diff --git a/frontend/src/HomePage/Configs/AppIcon.json b/frontend/src/HomePage/Configs/AppIcon.json index 7f2a55204c..06bf6d21db 100644 --- a/frontend/src/HomePage/Configs/AppIcon.json +++ b/frontend/src/HomePage/Configs/AppIcon.json @@ -1,19 +1,22 @@ { "iconList": [ "apps", - "business", - "calendar", - "chart-bar", - "code", - "cpu", - "database", - "file-analytics", - "flame", - "presentation", - "report-search", - "settings", - "users", - "world" + "archive", + "floppydisk", + "layers", + "folderupload", + "grid", + "home", + "sentfast", + "server", + "globe", + "share", + "shield", + "sun", + "table", + "menuhome", + "draghandle" + ], "defaultIcon": "apps" } \ No newline at end of file diff --git a/frontend/src/HomePage/ExportAppModal.jsx b/frontend/src/HomePage/ExportAppModal.jsx index a9ed236901..e75297f912 100644 --- a/frontend/src/HomePage/ExportAppModal.jsx +++ b/frontend/src/HomePage/ExportAppModal.jsx @@ -55,7 +55,9 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam return ( closeModal(false)} - contentClassName={`home-modal-component ${customClassName ? ` ${customClassName}` : ''} ${darkMode && 'dark'}`} + contentClassName={`home-modal-component ${customClassName ? ` ${customClassName}` : ''} ${ + darkMode && 'dark-theme' + }`} show={show} size="md" backdrop={true} diff --git a/frontend/src/HomePage/FolderMenu.jsx b/frontend/src/HomePage/FolderMenu.jsx index c49d26057b..d0949ecf1b 100644 --- a/frontend/src/HomePage/FolderMenu.jsx +++ b/frontend/src/HomePage/FolderMenu.jsx @@ -47,7 +47,7 @@ export const FolderMenu = function FolderMenu({ overlay={ diff --git a/frontend/src/HomePage/Folders.jsx b/frontend/src/HomePage/Folders.jsx index d07c0053b4..1137b8c470 100644 --- a/frontend/src/HomePage/Folders.jsx +++ b/frontend/src/HomePage/Folders.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useContext } from 'react'; import cx from 'classnames'; import { folderService } from '@/_services'; import { toast } from 'react-hot-toast'; @@ -7,6 +7,11 @@ import { FolderMenu } from './FolderMenu'; import { ConfirmDialog } from '@/_components'; import { useTranslation } from 'react-i18next'; import Skeleton from 'react-loading-skeleton'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { BreadCrumbContext } from '@/App/App'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import { SearchBox } from '@/_components/SearchBox'; +import _ from 'lodash'; export const Folders = function Folders({ folders, @@ -20,12 +25,7 @@ export const Folders = function Folders({ darkMode, }) { const [isLoading, setLoadingStatus] = useState(foldersLoading); - const { t } = useTranslation(); - - useEffect(() => { - setLoadingStatus(foldersLoading); - }, [foldersLoading]); - + const [showInput, setShowInput] = useState(false); const [showForm, setShowForm] = useState(false); const [isCreating, setCreationStatus] = useState(false); const [isDeleting, setDeletionStatus] = useState(false); @@ -36,6 +36,27 @@ export const Folders = function Folders({ const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false); const [showUpdateForm, setShowUpdateForm] = useState(false); const [activeFolder, setActiveFolder] = useState(currentFolder || {}); + const [filteredData, setFilteredData] = useState(folders); + + const { t } = useTranslation(); + const { updateSidebarNAV } = useContext(BreadCrumbContext); + + useEffect(() => { + setLoadingStatus(foldersLoading); + updateSidebarNAV('All apps'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [foldersLoading]); + + useEffect(() => { + setFilteredData(folders); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [folders]); + + const handleSearch = (e) => { + const value = e?.target?.value; + const filtered = folders.filter((item) => item?.name?.toLowerCase().includes(value?.toLowerCase())); + setFilteredData(filtered); + }; function saveFolder() { if (validateName()) { @@ -59,8 +80,13 @@ export const Folders = function Folders({ } function handleFolderChange(folder) { - setActiveFolder(folder); + if (_.isEmpty(folder)) { + setActiveFolder({}); + } else { + setActiveFolder(folder); + } folderChanged(folder); + updateSidebarNAV(folder?.name ?? 'All apps'); } function deleteFolder(folder) { @@ -83,6 +109,7 @@ export const Folders = function Folders({ setShowDeleteConfirmation(false); setDeletionStatus(false); foldersChanged(); + handleFolderChange({}); }) .catch(({ error }) => { toast.error(error); @@ -129,8 +156,12 @@ export const Folders = function Folders({ } } + function handleClose() { + setShowInput(false); + setFilteredData(folders); + } return ( -
+
-
-
- {t('homePage.foldersSection.folders', 'Folders')}{' '} - {!isLoading && folders && folders.length > 0 && `(${folders.length})`} -
- {canCreateFolder && ( -
{ - setNewFolderName(''); - setShowForm(true); - }} - data-cy="create-new-folder-button" - > - {t('homePage.foldersSection.createNewFolder', '+ Create new')} -
+
+ {!showInput ? ( + <> +
+ {t('homePage.foldersSection.filteredData', 'Folders')} + {!isLoading && filteredData && filteredData.length > 0 && `(${filteredData.length})`} +
+
+ {canCreateFolder && ( + <> +
{ + setNewFolderName(''); + setShowForm(true); + }} + data-cy="create-new-folder-button" + > + +
+
{ + setShowInput(true); + }} + > + +
+ + )} +
+ + ) : ( + )}
{!isLoading && ( -
+
handleFolderChange({})} data-cy="all-applications-link" > @@ -183,16 +239,14 @@ export const Folders = function Folders({ )} {isLoading && } {!isLoading && - folders && - folders.length > 0 && - folders.map((folder, index) => ( + filteredData && + filteredData.length > 0 && + filteredData.map((folder, index) => ( handleFolderChange(folder)} data-cy={`${folder.name.toLowerCase().replace(/\s+/g, '-')}-list-card`} > -
+
{`${folder.name}${folder.count > 0 ? ` (${folder.count})` : ''}`}
{(canDeleteFolder || canUpdateFolder) && ( @@ -225,7 +282,7 @@ export const Folders = function Folders({ } >
-
+
setNewFolderName(e.target.value)} @@ -241,22 +298,22 @@ export const Folders = function Folders({
- - +
diff --git a/frontend/src/HomePage/Header.jsx b/frontend/src/HomePage/Header.jsx index ff14c35211..0006373082 100644 --- a/frontend/src/HomePage/Header.jsx +++ b/frontend/src/HomePage/Header.jsx @@ -5,14 +5,15 @@ import { useTranslation } from 'react-i18next'; export default function Header({ onSearchSubmit, darkMode }) { const { t } = useTranslation(); return ( -
+
diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index b96b13b749..c36d12c7ed 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -17,6 +17,9 @@ import { withTranslation } from 'react-i18next'; import { sample } from 'lodash'; import ExportAppModal from './ExportAppModal'; import Footer from './Footer'; +import { OrganizationList } from '@/_components/OrganizationManager/List'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import BulkIcon from '@/_ui/Icon/bulkIcons/index'; import { getWorkspaceId } from '@/_helpers/utils'; import { withRouter } from '@/_hoc/withRouter'; @@ -305,7 +308,6 @@ class HomePageComponent extends React.Component { return; } this.fetchApps(1, this.state.currentFolder.id, key || ''); - this.fetchFolders(key || ''); }; addAppToFolder = () => { @@ -386,7 +388,7 @@ class HomePageComponent extends React.Component { onClick={() => this.setState({ appOperations: { ...appOperations, selectedIcon: icon } })} key={index} > - + )); }; @@ -488,12 +490,15 @@ class HomePageComponent extends React.Component { >
-
- {this.props.t('homePage.appCard.move', 'Move')} - {` "${appOperations?.selectedApp?.name}" `} +
+

+ {this.props.t('homePage.appCard.move', 'Move')} + {` "${appOperations?.selectedApp?.name}" `} +

+ {this.props.t('homePage.appCard.to', 'to')}
-
+
+
+
-
this.addSelectedAppsToGroup(groupPermission.id)} - data-cy={`${groupPermission.group - .toLowerCase() - .replace(/\s+/g, '-')}-group-select-search-add-button`} + data-cy="add-button" + disabled={selectedAppIds?.length == 0} + iconWidth="16" + fill={selectedAppIds.length != 0 ? '#FDFDFE' : this.props.darkMode ? '#4C5155' : '#C1C8CD'} + isLoading={isAddingApps} > - {this.props.t('globals.add', 'Add')} -
+ Add apps +
)}
-
- - - - - - - - +
- {this.props.t( - 'header.organization.menus.manageGroups.permissionResources.name', - 'Name' - )} - - {this.props.t( - 'header.organization.menus.manageGroups.permissionResources.permissions', - 'Permissions' - )} -
+ {groupPermission.group == 'admin' && ( +
+

+ Admin has edit access to all apps. These + are not editable +

+
+ )} +
+

App name

+

Permissions

+
+
{isLoadingGroup || isLoadingApps ? ( ) : ( - appsInGroup.map((app) => ( - - - + + + - - - )) + data-cy="delete-link" + className="delete-link" + > + + Remove + + + )} + + + )) + ) : ( +
+
+ +
+

No apps are added to the group

+ + Add app to the group to control permissions +
for users in this group +
+
+ )} + )}
@@ -425,77 +481,115 @@ class ManageGroupPermissionResourcesComponent extends React.Component {
{app.name} -
- - -
- {this.canAppGroupPermission(app, groupPermission.id, 'view') && ( -
-
{app.name} +
+ + +
+
+ +
+
+ {groupPermission.group !== 'admin' && ( + { + this.removeAppFromGroup(groupPermission.id, app.id, app.name); }} - disabled={groupPermission.group === 'admin'} - checked={this.canAppGroupPermission( - app, - groupPermission.id, - 'hideFromDashboard' - )} - /> - Hide from dashboard - - - )} - - {groupPermission.group !== 'admin' && ( - { - this.removeAppFromGroup(groupPermission.id, app.id, app.name); - }} - className="text-danger" - data-cy="delete-link" - > - {this.props.t('globals.delete', 'Delete')} - - )} -
@@ -507,8 +601,8 @@ class ManageGroupPermissionResourcesComponent extends React.Component {
{groupPermission?.group !== 'all_users' && (
-
- +
-
this.addSelectedUsersToGroup(groupPermission.id, selectedUsers)} - data-cy={`${groupPermission.group - .toLowerCase() - .replace(/\s+/g, '-')}-group-multi-select-search-add-button`} + disabled={selectedUsers.length === 0} + leftIcon="plus" + fill={selectedUsers.length !== 0 ? '#3E63DD' : this.props.darkMode ? '#131620' : '#C1C8CD'} + iconWidth="16" + className="add-users-button" + isLoading={isAddingUsers} > - {this.props.t('globals.add', 'Add')} -
+ Add users +
-
- Selected Users: -
+
Selected Users:
{this.generateSelection(selectedUsers)}
@@ -559,89 +651,100 @@ class ManageGroupPermissionResourcesComponent extends React.Component { )}
-
- - - - - - - - - - {isLoadingGroup || isLoadingUsers ? ( - - - - - - ) : ( - usersInGroup.map((user) => ( - - - - - - )) - )} - -
- {this.props.t( - 'header.organization.menus.manageGroups.permissionResources.name', - 'name' - )} - - {this.props.t( - 'header.organization.menus.manageGroups.permissionResources.email', - 'email' - )} -
-
-
-
-
-
-
-
-
{`${user?.first_name ?? ''} ${user?.last_name ?? ''}`}{user.email} - {groupPermission.group !== 'all_users' && ( - { - this.removeUserFromGroup(groupPermission.id, user.id); - }} - > - {this.props.t('globals.delete', 'Delete')} - - )} -
+ {groupPermission.group == 'all_users' && ( +
+

+ All users include every users in the app. + This list is not editable +

+
+ )} +
+

+ User name +

+

+ Email id +

+

{/* DO NOT REMOVE FOR TABLE ALIGNMENT */}
+
+ {isLoadingGroup || isLoadingUsers ? ( + + +
+
+
+ + +
+ + +
+ + + ) : ( + usersInGroup.map((user) => ( +
+

+

+ {`${user?.first_name?.[0] ?? ''} ${user?.last_name?.[0] ?? ''}`} +
+ {`${user?.first_name ?? ''} ${user?.last_name ?? ''}`} +

+

+ {user.email} +

+

+ {groupPermission.group !== 'all_users' && ( + + { + this.removeUserFromGroup(groupPermission.id, user.id); + }} + > + {this.props.t('globals.delete', 'Delete')} + + + )} +

+
+ )) + )} +
{/* Permissions Tab */} -
+ +
-
+
diff --git a/frontend/src/ManageGroupPermissions/ManageGroupPermissions.jsx b/frontend/src/ManageGroupPermissions/ManageGroupPermissions.jsx index a3cf9c2049..a94da87b87 100644 --- a/frontend/src/ManageGroupPermissions/ManageGroupPermissions.jsx +++ b/frontend/src/ManageGroupPermissions/ManageGroupPermissions.jsx @@ -2,11 +2,13 @@ import React from 'react'; import { groupPermissionService } from '@/_services'; import { ConfirmDialog } from '@/_components'; import { toast } from 'react-hot-toast'; -import { Link } from 'react-router-dom'; import { withTranslation } from 'react-i18next'; import { ManageGroupPermissionResources } from '@/ManageGroupPermissionResources'; import ErrorBoundary from '@/Editor/ErrorBoundary'; - +import Modal from '../HomePage/Modal'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import FolderList from '@/_ui/FolderList/FolderList'; +import { Loader } from '../ManageSSO/Loader'; class ManageGroupPermissionsComponent extends React.Component { constructor(props) { super(props); @@ -24,6 +26,7 @@ class ManageGroupPermissionsComponent extends React.Component { groupToBeUpdated: null, isSaveBtnDisabled: false, selectedGroupPermissionId: null, + selectedGroup: 'All Users', }; } @@ -31,7 +34,15 @@ class ManageGroupPermissionsComponent extends React.Component { this.fetchGroups(); } - fetchGroups = () => { + findCurrentGroupDetails = (data) => { + let currentUpdatedGroup = data.group_permissions.find((item) => { + return item.group == this.state.newGroupName; + }); + this.setState({ selectedGroup: currentUpdatedGroup.group }); + return currentUpdatedGroup.id; + }; + + fetchGroups = (type = 'admin') => { this.setState({ isLoading: true, }); @@ -42,7 +53,12 @@ class ManageGroupPermissionsComponent extends React.Component { this.setState({ groups: data.group_permissions, isLoading: false, - selectedGroupPermissionId: data.group_permissions[0].id, + selectedGroupPermissionId: + type == 'admin' + ? data.group_permissions[0].id + : type == 'current' + ? this.findCurrentGroupDetails(data) + : data.group_permissions.at(-1).id, }); }) .catch(({ error }) => { @@ -69,8 +85,10 @@ class ManageGroupPermissionsComponent extends React.Component { switch (groupName) { case 'all_users': return 'All Users'; + case 'admin': return 'Admin'; + default: return groupName; } @@ -85,9 +103,10 @@ class ManageGroupPermissionsComponent extends React.Component { creatingGroup: false, showNewGroupForm: false, newGroupName: null, + selectedGroup: this.state.newGroupName, }); toast.success('Group has been created'); - this.fetchGroups(); + this.fetchGroups('new'); }) .catch(({ error }) => { toast.error(error); @@ -129,6 +148,7 @@ class ManageGroupPermissionsComponent extends React.Component { .then(() => { toast.success('Group deleted successfully'); this.fetchGroups(); + this.setState({ selectedGroup: 'All Users', isDeletingGroup: false }); }) .catch(({ error }) => { toast.error(error); @@ -139,17 +159,16 @@ class ManageGroupPermissionsComponent extends React.Component { }; executeGroupUpdation = () => { - this.setState({ isUpdatingGroupName: true }); + this.setState({ isUpdatingGroupName: true, selectedGroup: this.state.newGroupName }); groupPermissionService .update(this.state.groupToBeUpdated?.id, { name: this.state.newGroupName }) .then(() => { toast.success('Group name updated successfully'); - this.fetchGroups(); + this.fetchGroups('current'); this.setState({ isUpdatingGroupName: false, groupToBeUpdated: null, showGroupNameUpdateForm: false, - newGroupName: null, }); }) .catch(({ error }) => { @@ -174,217 +193,156 @@ class ManageGroupPermissionsComponent extends React.Component { return (
- this.executeGroupDeletion()} - onCancel={() => this.cancelDeleteGroupDialog()} - darkMode={this.props.darkMode} - /> - -
-
-
-
-
-
-

- {this.props.t('header.organization.menus.manageGroups.permissions.userGroups', 'User Groups')} -

-
-
- {!showNewGroupForm && !showGroupNameUpdateForm && ( -
this.setState({ showNewGroupForm: true, isSaveBtnDisabled: true })} - data-cy="create-new-group-button" - > - {this.props.t( - 'header.organization.menus.manageGroups.permissions.createNewGroup', - 'Create new group' - )} -
- )} -
-
-
+
+ this.executeGroupDeletion()} + onCancel={() => this.cancelDeleteGroupDialog()} + darkMode={this.props.darkMode} + /> +
+

{groups?.length} Groups

+ {!showNewGroupForm && !showGroupNameUpdateForm && ( + { + e.preventDefault(); + this.setState({ newGroupName: null, showNewGroupForm: true, isSaveBtnDisabled: true }); + }} + data-cy="create-new-group-button" + leftIcon="plus" + isLoading={isLoading} + iconWidth="16" + fill={'#FDFDFE'} + > + {this.props.t( + 'header.organization.menus.manageGroups.permissions.createNewGroup', + 'Create new group' + )} + + )}
-
- {(showNewGroupForm || showGroupNameUpdateForm) && ( -
-
-
-

- {showGroupNameUpdateForm - ? this.props.t( - 'header.organization.menus.manageGroups.permissions.updateGroup', - 'Update group' - ) - : this.props.t( - 'header.organization.menus.manageGroups.permissions.addNewGroup', - 'Add new group' - )} -

-
-
- { - e.preventDefault(); - if (showNewGroupForm) { - this.createGroup(); - } else { - this.executeGroupUpdation(); - } + + this.setState({ + showNewGroupForm: false, + showGroupNameUpdateForm: false, + newGroupName: null, + }) + } + title={ + showGroupNameUpdateForm + ? this.props.t('header.organization.menus.manageGroups.permissions.updateGroup', 'Update group') + : this.props.t('header.organization.menus.manageGroups.permissions.addNewGroup', 'Add new group') + } + > + { + e.preventDefault(); + if (showNewGroupForm) { + this.createGroup(); + } else { + this.executeGroupUpdation(); + } + }} + > +
+
+
+ { + this.changeNewGroupName(e.target.value); }} - > -
-
-
- { - this.changeNewGroupName(e.target.value); - }} - value={this.state.newGroupName} - data-cy="group-name-input" - /> -
-
-
-
- - -
- + value={this.state.newGroupName} + data-cy="group-name-input" + autoFocus + />
- )} - {!showNewGroupForm && !showGroupNameUpdateForm && ( -
-
-
- - - - - - - - - {isLoading ? ( - - {Array.from(Array(2)).map((index) => ( - - - - - - ))} - - ) : ( - - {groups.map((permissionGroup) => ( - - - - - - ))} - - )} -
- {this.props.t('header.organization.menus.manageGroups.permissions.name', 'Name')} -
-
-
-
-
-
-
-
-
this.setState({ selectedGroupPermissionId: permissionGroup.id })} - data-cy={`${this.humanizeifDefaultGroupName(permissionGroup.group) - .toLowerCase() - .replace(/\s+/g, '-')}-group-link`} - > - {this.humanizeifDefaultGroupName(permissionGroup.group)} - - {permissionGroup.group !== 'admin' && permissionGroup.group !== 'all_users' && ( -
- this.updateGroupName(permissionGroup)} - data-cy={`${this.humanizeifDefaultGroupName(permissionGroup.group) - .toLowerCase() - .replace(/\s+/g, '-')}-group-update-link`} - > - {this.props.t('globals.update', 'Update')} - - this.deleteGroup(permissionGroup.id)} - data-cy={`${this.humanizeifDefaultGroupName(permissionGroup.group) - .toLowerCase() - .replace(/\s+/g, '-')}-group-delete-link`} - > - {this.props.t('globals.delete', 'Delete')} - -
- )} -
-
-
-
+
+ + this.setState({ + showNewGroupForm: false, + showGroupNameUpdateForm: false, + newGroupName: null, + }) + } + disabled={creatingGroup} + data-cy="cancel-button" + variant="tertiary" + > + {this.props.t('globals.cancel', 'Cancel')} + + + {showGroupNameUpdateForm + ? this.props.t('globals.save', 'Save') + : this.props.t('header.organization.menus.manageGroups.permissions.createGroup', 'Create Group')} + +
+ + + + {!showNewGroupForm && !showGroupNameUpdateForm && ( +
+
+ {groups.map((permissionGroup) => { + return ( + { + this.setState({ + selectedGroupPermissionId: permissionGroup.id, + selectedGroup: this.humanizeifDefaultGroupName(permissionGroup.group), + }); + }} + className="groups-folder-list" + > + {this.humanizeifDefaultGroupName(permissionGroup.group)} + + ); + })} +
+ +
+ {isLoading ? ( + + ) : ( -
+ )}
- )} -
+
+ )}
diff --git a/frontend/src/ManageOrgUsers/FileDropzone.jsx b/frontend/src/ManageOrgUsers/FileDropzone.jsx new file mode 100644 index 0000000000..acf1e13c81 --- /dev/null +++ b/frontend/src/ManageOrgUsers/FileDropzone.jsx @@ -0,0 +1,68 @@ +import React, { useState } from 'react'; +import { useDropzone } from 'react-dropzone'; +import BulkIcon from '@/_ui/Icon/BulkIcons'; +import { toast } from 'react-hot-toast'; + +export function FileDropzone({ handleClick, hiddenFileInput, errors, handleFileChange, inviteBulkUsers, onDrop }) { + const { getRootProps, getInputProps, isDragActive, acceptedFiles } = useDropzone({ + onDrop, + accept: 'text/csv', + }); + const [fileData, setFileData] = useState(); + + const files = + acceptedFiles.length > 0 + ? acceptedFiles + : acceptedFiles?.map((file) => ( +
  • + {file.path} - {file.size} bytes +
  • + )); + return ( +
    +
    +
    +
    + +
    +

    + Select a CSV file to upload +

    + + {!isDragActive ? 'Or drag and drop it here' : ''} + + { + const file = e.target.files[0]; + setFileData(file); + if (Math.round(file.size / 1024) > 1024) { + toast.error('File size cannot exceed more than 1MB'); + e.target.value = null; + } else { + handleFileChange(file); + } + }} + accept=".csv" + type="file" + className="form-control" + data-cy="input-field-bulk-upload" + /> + + {errors['file']} + +
      {files}
    + {fileData?.name &&
      {` ${fileData?.name} - ${fileData?.size} bytes`}
    } +
    +
    +
    + ); +} diff --git a/frontend/src/ManageOrgUsers/InviteUsersForm.jsx b/frontend/src/ManageOrgUsers/InviteUsersForm.jsx new file mode 100644 index 0000000000..28546a9ebf --- /dev/null +++ b/frontend/src/ManageOrgUsers/InviteUsersForm.jsx @@ -0,0 +1,197 @@ +import React, { useState, useCallback, useRef } from 'react'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { useTranslation } from 'react-i18next'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import { toast } from 'react-hot-toast'; +import { useDropzone } from 'react-dropzone'; +import { FileDropzone } from './FileDropzone'; + +function InviteUsersForm({ + onClose, + createUser, + changeNewUserOption, + errors, + fields, + handleFileChange, + uploadingUsers, + onCancel, + inviteBulkUsers, +}) { + const { t } = useTranslation(); + const [activeTab, setActiveTab] = useState(1); + + const hiddenFileInput = useRef(null); + const { acceptedFiles } = useDropzone({ + onDrop, + accept: 'text/csv', + }); + + const onDrop = useCallback((acceptedFiles) => { + const file = acceptedFiles[0]; + if (Math.round(file.size / 1024) > 1024) { + toast.error('File size cannot exceed more than 1MB'); + } else { + handleFileChange(file); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleClick = () => { + hiddenFileInput.current.click(); + }; + + const files = acceptedFiles.map((file) => ( +
  • + {file.path} - {file.size} bytes +
  • + )); + + return ( +
    +
    +
    +
    +
    +

    + {t('header.organization.menus.manageUsers.addNewUser', 'Add new user')} +

    +
    onClose()} style={{ cursor: 'pointer' }} data-cy="close-button"> + +
    +
    +
    +
    + + +
    +
    +
    + {activeTab == 1 ? ( +
    +
    +
    + +
    +
    + + + {errors['fullName']} + +
    +
    +
    + +
    + + + {errors['email']} + +
    +
    +
    +
    +
    + ) : ( +
    +
    +
    +
    + +
    +
    +

    + Download the ToolJet template to add user details or format your file in the same as the template. + ToolJet won’t be able to recognise files in any other format.{' '} +

    + + Download Template + +
    +
    +
    + +
    + )} +
    + { + onCancel(); + onClose(); + }} + variant="tertiary" + > + {t('globals.cancel', 'Cancel')} + + + + {activeTab == 1 ? t('header.organization.menus.manageUsers.inviteUsers', 'Invite Users') : 'Upload users'} + +
    +
    +
    +
    + ); +} +export default InviteUsersForm; diff --git a/frontend/src/ManageOrgUsers/ManageOrgUsers.jsx b/frontend/src/ManageOrgUsers/ManageOrgUsers.jsx index 23ce5e8de4..5865918f9e 100644 --- a/frontend/src/ManageOrgUsers/ManageOrgUsers.jsx +++ b/frontend/src/ManageOrgUsers/ManageOrgUsers.jsx @@ -7,6 +7,8 @@ import urlJoin from 'url-join'; import ErrorBoundary from '@/Editor/ErrorBoundary'; import UsersTable from '../../ee/components/UsersPage/UsersTable'; import UsersFilter from '../../ee/components/UsersPage/UsersFilter'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import ManageOrgUsersDrawer from './ManageOrgUsersDrawer'; class ManageOrgUsersComponent extends React.Component { constructor(props) { @@ -14,8 +16,6 @@ class ManageOrgUsersComponent extends React.Component { this.state = { isLoading: true, - showNewUserForm: false, - showUploadUserForm: false, creatingUser: false, uploadingUsers: false, newUser: {}, @@ -29,6 +29,7 @@ class ManageOrgUsersComponent extends React.Component { currentPage: 1, options: {}, file: null, + isInviteUsersDrawerOpen: false, }; } @@ -41,11 +42,8 @@ class ManageOrgUsersComponent extends React.Component { handleValidation() { let fields = this.state.fields; let errors = {}; - if (!fields['firstName']) { - errors['firstName'] = 'This field is required'; - } - if (!fields['lastName']) { - errors['lastName'] = 'This field is required'; + if (!fields['fullName']) { + errors['fullName'] = 'This field is required'; } if (!fields['email']) { errors['email'] = 'This field is required'; @@ -67,10 +65,6 @@ class ManageOrgUsersComponent extends React.Component { return Object.keys(errors).length === 0; } - componentDidMount() { - this.fetchUsers(1); - } - fetchUsers = (page = 1, options = {}) => { this.setState({ options, @@ -146,7 +140,7 @@ class ManageOrgUsersComponent extends React.Component { this.fetchUsers(); this.setState({ uploadingUsers: false, - showUploadUserForm: false, + isInviteUsersDrawerOpen: false, file: null, }); }) @@ -161,15 +155,25 @@ class ManageOrgUsersComponent extends React.Component { this.setState({ file }); }; + handleNameSplit = (fullName) => { + const [first, last] = fullName.split(' '); + let fields = this.state.fields; + fields['firstName'] = first; + fields['lastName'] = last; + this.setState({ + fields, + }); + }; + createUser = (event) => { event.preventDefault(); if (this.handleValidation()) { - if (!this.state.fields.firstName?.trim() || !this.state.fields.lastName?.trim()) { - toast.error('First and last name should not be empty'); + if (!this.state.fields.fullName?.trim()) { + toast.error('Name should not be empty'); return; } - + this.handleNameSplit(this.state.fields['fullName']); let fields = {}; Object.keys(this.state.fields).map((key) => { fields[key] = ''; @@ -191,8 +195,8 @@ class ManageOrgUsersComponent extends React.Component { this.fetchUsers(); this.setState({ creatingUser: false, - showNewUserForm: false, fields: fields, + isInviteUsersDrawerOpen: false, }); }) .catch(({ error }) => { @@ -200,7 +204,7 @@ class ManageOrgUsersComponent extends React.Component { this.setState({ creatingUser: false }); }); } else { - this.setState({ creatingUser: false, showNewUserForm: true, file: null }); + this.setState({ creatingUser: false, file: null, isInviteUsersDrawerOpen: true }); } }; @@ -228,249 +232,79 @@ class ManageOrgUsersComponent extends React.Component { filterList = (options) => { this.fetchUsers(1, options); }; + setIsInviteUsersDrawerOpen = (val) => { + this.setState({ isInviteUsersDrawerOpen: val }); + }; + + onCancel = () => { + this.setState({ + errors: {}, + file: null, + }); + }; render() { - const { - isLoading, - showNewUserForm, - showUploadUserForm, - creatingUser, - uploadingUsers, - users, - archivingUser, - unarchivingUser, - meta, - } = this.state; + const { isLoading, uploadingUsers, users, archivingUser, unarchivingUser, meta } = this.state; return (
    + {this.state.isInviteUsersDrawerOpen && ( + + )} +
    -
    -
    -
    -
    -
    -

    - {this.props.t('header.organization.menus.manageUsers.usersAndPermission', 'Users & Permissions')} -

    +
    +
    +
    +
    + {meta?.total_count} users
    -
    - {!showUploadUserForm && !showNewUserForm && ( -
    this.setState({ showUploadUserForm: true })} - data-cy="invite-bulk-user-button" - > - Invite bulk users -
    - )} - {!showNewUserForm && !showUploadUserForm && ( -
    this.setState({ showNewUserForm: true })} - data-cy="invite-new-user" - > - {this.props.t('header.organization.menus.manageUsers.inviteNewUser', 'Invite new user')} -
    - )} +
    + this.setState({ isInviteUsersDrawerOpen: true })} + leftIcon="usergroup" + fill={'#FDFDFE'} + > + {this.props.t('header.organization.menus.manageUsers.addNewUser', 'Add users')} +
    - {showNewUserForm && ( -
    -
    -
    -

    - {this.props.t('header.organization.menus.manageUsers.addNewUser', 'Add new user')} -

    -
    -
    -
    -
    -
    -
    - - - {this.state.errors['firstName']} - -
    -
    - - - {this.state.errors['lastName']} - -
    -
    -
    -
    - -
    - - - {this.state.errors['email']} - -
    -
    -
    - - -
    -
    -
    + this.fetchUsers()} + /> + + {users?.length === 0 && ( +
    +
    + + No result found + + + Try changing the filters +
    )} - {showUploadUserForm && ( -
    -
    -
    -

    - Upload Users -

    -
    -
    -
    -
    -
    -
    - { - const file = e.target.files[0]; - if (Math.round(file.size / 1024) > 1024) { - toast.error('File size cannot exceed more than 1MB'); - e.target.value = null; - } else { - this.handleFileChange(file); - } - }} - accept=".csv" - type="file" - className="form-control" - data-cy="bulk-user-upload-input" - /> - - {this.state.errors['file']} - -
    - -
    -
    -
    - - -
    -
    -
    -
    -
    - )} - - {!showNewUserForm && !showUploadUserForm && ( - this.fetchUsers()} - /> - )} - - {users?.length === 0 && !showNewUserForm && !showUploadUserForm && ( -
    - No result found - Try changing the filters -
    - )} - - {!showNewUserForm && !showUploadUserForm && users?.length !== 0 && ( + {users?.length !== 0 && ( { + return ( + setIsInviteUsersDrawerOpen(false)} + position="right" + > + setIsInviteUsersDrawerOpen(false)} + /> + + ); +}; + +export default ManageOrgUsersDrawer; diff --git a/frontend/src/ManageOrgVars/ManageOrgVars.jsx b/frontend/src/ManageOrgVars/ManageOrgVars.jsx index bc0aba199c..028017d084 100644 --- a/frontend/src/ManageOrgVars/ManageOrgVars.jsx +++ b/frontend/src/ManageOrgVars/ManageOrgVars.jsx @@ -2,10 +2,11 @@ import React from 'react'; import { authenticationService, orgEnvironmentVariableService } from '@/_services'; import { ConfirmDialog } from '@/_components'; import { toast } from 'react-hot-toast'; -import VariableForm from './VariableForm'; import VariablesTable from './VariablesTable'; // eslint-disable-next-line import/no-unresolved import { withTranslation } from 'react-i18next'; +import ManageOrgVarsDrawer from './ManageOrgVarsDrawer'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; class ManageOrgVarsComponent extends React.Component { constructor(props) { super(props); @@ -22,6 +23,7 @@ class ManageOrgVarsComponent extends React.Component { }, errors: {}, showVariableDeleteConfirmation: false, + isManageVarDrawerOpen: false, }; this.tableRef = React.createRef(null); @@ -34,6 +36,7 @@ class ManageOrgVarsComponent extends React.Component { onEditBtnClicked = (variable) => { this.setState({ showVariableForm: true, + isManageVarDrawerOpen: true, errors: {}, fields: { ...variable, @@ -50,6 +53,7 @@ class ManageOrgVarsComponent extends React.Component { onCancelBtnClicked = () => { this.setState({ showVariableForm: false, + isManageVarDrawerOpen: false, newVariable: {}, fields: { encryption: false, variable_type: 'client' }, selectedVariableId: null, @@ -135,6 +139,7 @@ class ManageOrgVarsComponent extends React.Component { this.setState({ addingVar: false, showVariableForm: false, + isManageVarDrawerOpen: false, fields: fields, selectedVariableId: null, }); @@ -159,6 +164,7 @@ class ManageOrgVarsComponent extends React.Component { this.setState({ addingVar: false, showVariableForm: false, + isManageVarDrawerOpen: false, fields: fields, selectedVariableId: null, }); @@ -169,7 +175,7 @@ class ManageOrgVarsComponent extends React.Component { }); } } else { - this.setState({ addingVar: false, showVariableForm: true }); + this.setState({ addingVar: false, showVariableForm: true, isManageVarDrawerOpen: true }); } }; @@ -239,9 +245,12 @@ class ManageOrgVarsComponent extends React.Component { authenticationService.currentSessionValue.group_permissions ); }; + setIsManageVarDrawerOpen = (val) => { + this.setState({ isManageVarDrawerOpen: val }); + }; render() { - const { isLoading, showVariableForm, addingVar, variables } = this.state; + const { isLoading, addingVar, variables, isManageVarDrawerOpen } = this.state; return (
    -
    -
    -
    -
    -

    - {this.props.t('globals.environmentVar', 'Workspace Variables')} -

    -
    -
    - {!showVariableForm && this.canCreateVariable() && ( -
    this.setState({ showVariableForm: true, errors: {} })} +
    +
    +
    + {!isManageVarDrawerOpen && this.canCreateVariable() && ( + this.setState({ isManageVarDrawerOpen: true, errors: {} })} + className="add-new-variables-button" > {this.props.t( 'header.organization.menus.manageSSO.environmentVar.addNewVariable', 'Add new variable' )} -
    + )}
    @@ -291,8 +295,10 @@ class ManageOrgVarsComponent extends React.Component {
    - {showVariableForm ? ( - { + return ( + setIsManageVarDrawerOpen(false)} + position="right" + > + + + ); +}; + +export default ManageOrgVarsDrawer; diff --git a/frontend/src/ManageOrgVars/VariableForm.jsx b/frontend/src/ManageOrgVars/VariableForm.jsx index 879fc9620b..2334ea4f4f 100644 --- a/frontend/src/ManageOrgVars/VariableForm.jsx +++ b/frontend/src/ManageOrgVars/VariableForm.jsx @@ -1,6 +1,7 @@ import React from 'react'; import Select from '@/_ui/Select'; import { withTranslation } from 'react-i18next'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; class VariableForm extends React.Component { constructor(props) { super(props); @@ -8,140 +9,127 @@ class VariableForm extends React.Component { render() { return ( -
    -
    -
    -

    - {!this.props.selectedVariableId - ? this.props.t( - 'header.organization.menus.manageSSO.environmentVar.variableForm.addNewVariable', - 'Add new variable' - ) - : this.props.t( - 'header.organization.menus.manageSSO.environmentVar.variableForm.updatevariable', - 'Update variable' - )} -

    -
    -
    -
    -
    -
    -
    - - - - {this.props.errors['variable_name']} - -
    -
    - - - - {this.props.errors['value']} - -
    -
    -
    -
    -
    -
    - - {this.props.selectedVariableId ? ( - {this.props.fields['variable_type']} - ) : ( - -
    - -
    - this.props.handleEncryptionToggle(e)} - checked={ - this.props.fields['variable_type'] === 'server' ? true : this.props.fields['encryption'] - } - data-cy="enable-toggle" - /> -
    + name="variable_name" + onChange={this.props.changeNewVariableOption.bind(this, 'variable_name')} + value={this.props.fields['variable_name']} + data + autoFocus + /> + {this.props.errors['variable_name']} +
    +
    + + + {this.props.errors['value']} +
    +
    +
    +
    +
    +
    + + {this.props.selectedVariableId ? ( + {this.props.fields['variable_type']} + ) : ( + this.props.handleEncryptionToggle(e)} + checked={this.props.fields['variable_type'] === 'server' ? true : this.props.fields['encryption']} + />
    -
    - - -
    - -
    +
    + +
    +
    + this.props.onCancelBtnClicked()} data-cy="cancel-button" variant="tertiary"> + {this.props.t('globals.cancel', 'Cancel')} + + + {' '} + {!this.props.selectedVariableId + ? this.props.t( + 'header.organization.menus.manageSSO.environmentVar.variableForm.addVariable', + 'Add variable' + ) + : this.props.t('globals.save', 'Save')} +
    ); diff --git a/frontend/src/ManageOrgVars/VariablesTable.jsx b/frontend/src/ManageOrgVars/VariablesTable.jsx index 32b053f455..0ec241a88c 100644 --- a/frontend/src/ManageOrgVars/VariablesTable.jsx +++ b/frontend/src/ManageOrgVars/VariablesTable.jsx @@ -1,5 +1,6 @@ import React from 'react'; import { withTranslation } from 'react-i18next'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; import { Tooltip } from 'react-tooltip'; class VariablesTable extends React.Component { @@ -18,7 +19,7 @@ class VariablesTable extends React.Component { const { isLoading, variables } = this.props; return (
    -
    +
    @@ -134,15 +135,7 @@ class VariablesTable extends React.Component { data-tooltip-content="Update" >
    - +
    @@ -156,19 +149,9 @@ class VariablesTable extends React.Component { data-cy={`${variable.variable_name .toLowerCase() .replace(/\s+/g, '-')}-workspace-variable-delete-button`} - data-tooltip-id="tooltip-for-delete" - data-tooltip-content="Delete" >
    - +
    diff --git a/frontend/src/ManageSSO/Form.jsx b/frontend/src/ManageSSO/Form.jsx index a1d6fd2d7d..8c29743683 100644 --- a/frontend/src/ManageSSO/Form.jsx +++ b/frontend/src/ManageSSO/Form.jsx @@ -27,7 +27,7 @@ export function Form({ settings, updateData, darkMode }) { }; return ( -
    +
    {t('header.organization.menus.manageSSO.passwordLogin', 'Password Login')} - + {enabled ? t('globals.enabled', 'Enabled') : t('globals.disabled', 'Disabled')}
    diff --git a/frontend/src/ManageSSO/GeneralSettings.jsx b/frontend/src/ManageSSO/GeneralSettings.jsx index fa5b688910..baa7025315 100644 --- a/frontend/src/ManageSSO/GeneralSettings.jsx +++ b/frontend/src/ManageSSO/GeneralSettings.jsx @@ -3,13 +3,36 @@ import React, { useState } from 'react'; import { toast } from 'react-hot-toast'; import { copyToClipboard } from '@/_helpers/appUtils'; import { useTranslation } from 'react-i18next'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import { ConfirmDialog } from '@/_components'; -export function GeneralSettings({ settings, updateData, instanceSettings }) { +export function GeneralSettings({ settings, updateData, instanceSettings, darkMode }) { const [enableSignUp, setEnableSignUp] = useState(settings?.enable_sign_up || false); const [inheritSSO, setInheritSSO] = useState(settings?.inherit_s_s_o || false); const [domain, setDomain] = useState(settings?.domain || ''); const [isSaving, setSaving] = useState(false); const { t } = useTranslation(); + const passwordSettings = settings?.sso_configs?.find((obj) => obj.sso === 'form'); + const [enabled, setEnabled] = useState(passwordSettings?.enabled || false); + const [showDisablingPasswordConfirmation, setShowDisablingPasswordConfirmation] = useState(false); + + const changeStatus = () => { + organizationService.editOrganizationConfigs({ type: 'form', enabled: !enabled }).then( + (data) => { + const enabled_tmp = !enabled; + setEnabled(enabled_tmp); + updateData('form', { id: data.id, enabled: enabled_tmp }); + toast.success(`${enabled_tmp ? 'Enabled' : 'Disabled'} Password login`, { position: 'top-center' }); + setShowDisablingPasswordConfirmation(false); + }, + () => { + toast.error('Error while saving SSO configurations', { + position: 'top-center', + }); + } + ); + }; const reset = () => { setEnableSignUp(settings?.enable_sign_up || false); @@ -39,65 +62,23 @@ export function GeneralSettings({ settings, updateData, instanceSettings }) { ); }; - const tickIcon = () => { - return ( - - - - - ); - }; - - const crossIcon = () => { - return ( - - - - - - ); - }; - const ssoButtons = (type) => { return ( -
    -
    {inheritSSO ? tickIcon() : crossIcon()}
    +
    ); }; return ( -
    +
    {t('header.organization.menus.manageSSO.generalSettings.title', 'General Settings')}
    -
    +
    -
    +
    {t( 'header.organization.menus.manageSSO.generalSettings.newAccountWillBeCreated', `New account will be created for user's first time SSO sign in` @@ -134,42 +115,51 @@ export function GeneralSettings({ settings, updateData, instanceSettings }) { {t('header.organization.menus.manageSSO.generalSettings.allowDefaultSso', `Allow default SSO`)} -
    - {instanceSettings.google.enabled && ssoButtons('google')} - {instanceSettings.git.enabled && ssoButtons('git')} -
    -
    -
    +
    +
    {t( 'header.organization.menus.manageSSO.generalSettings.ssoAuth', `Allow users to authenticate via default SSO. Default SSO configurations can be overridden by workspace level SSO.` )}
    +
    + +

    Default options

    +
    + {instanceSettings.google.enabled && ssoButtons('google')} + {instanceSettings.git.enabled && ssoButtons('git')} +
    +
    )} -
    +
    -
    - setDomain(e.target.value)} - data-cy="allowed-domain-input" - /> -
    -
    -
    - {t( - 'header.organization.menus.manageSSO.generalSettings.supportMultiDomains', - `Support multiple domains. Enter domain names separated by comma. example: tooljet.com,tooljet.io,yourorganization.com` - )} -
    + setDomain(e.target.value)} + data-cy="allowed-domain-input" + /> +
    +
    +
    + {t( + 'header.organization.menus.manageSSO.generalSettings.supportMultiDomains', + `Support multiple domains. Enter domain names separated by comma. example: tooljet.com,tooljet.io,yourorganization.com` + )}
    @@ -177,20 +167,13 @@ export function GeneralSettings({ settings, updateData, instanceSettings }) { {t('header.organization.menus.manageSSO.generalSettings.loginUrl', `Login URL`)} -
    +

    {`${window.public_config?.TOOLJET_HOST}/login/${authenticationService?.currentSessionValue?.current_organization_id}`}

    - copyFunction('login-url')} - src={`assets/images/icons/copy-dark.svg`} - width="22" - height="22" - className="sso-copy" - data-cy="copy-icon" - /> + copyFunction('login-url')} />
    -
    +
    {t( 'header.organization.menus.manageSSO.generalSettings.workspaceLogin', @@ -199,22 +182,62 @@ export function GeneralSettings({ settings, updateData, instanceSettings }) {
    -
    - - + + changeStatus()} + onCancel={() => setShowDisablingPasswordConfirmation(false)} + darkMode={darkMode} + /> +
    +
    + +

    Danger zone

    +
    +
    + +
    +
    + Disable password login only if your SSO is configured otherwise you will get logged out. +
    +
    +
    + +
    + + {t('globals.cancel', 'Cancel')} + + + + {t('globals.savechanges', 'Save')} + +
    ); } diff --git a/frontend/src/ManageSSO/Git.jsx b/frontend/src/ManageSSO/Git.jsx index 77869a2a7b..3841286e4e 100644 --- a/frontend/src/ManageSSO/Git.jsx +++ b/frontend/src/ManageSSO/Git.jsx @@ -3,6 +3,9 @@ import { organizationService } from '@/_services'; import { toast } from 'react-hot-toast'; import { copyToClipboard } from '@/_helpers/appUtils'; import { useTranslation } from 'react-i18next'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import Toggle from '@/_ui/Toggle/index'; export function Git({ settings, updateData }) { const [enabled, setEnabled] = useState(settings?.enabled || false); @@ -69,35 +72,30 @@ export function Git({ settings, updateData }) { }; return ( -
    +
    +
    + +
    - {t('header.organization.menus.manageSSO.github.title', 'Github')} - + {enabled ? t('globals.enabled', 'Enabled') : t('globals.disabled', 'Disabled')}
    -
    - -
    -
    +
    -
    +
    -
    -
    +
    +
    {t('header.organization.menus.manageSSO.github.requiredGithub', 'Required if GitHub is self hosted')}
    @@ -117,7 +115,7 @@ export function Git({ settings, updateData }) { -
    +
    -
    +
    {t('header.organization.menus.manageSSO.github.redirectUrl', 'Redirect URL')} -
    +

    {`${window.public_config?.TOOLJET_HOST}/sso/git/${configId}`}

    - copyFunction('redirect-url')} - src={`assets/images/icons/copy-dark.svg`} - width="22" - height="22" - className="sso-copy" - /> + copyFunction('redirect-url')} />
    )} -
    - - -
    +
    + + {t('globals.cancel', 'Cancel')} + + + + {t('globals.savechanges', 'Save changes')} + +
    ); } diff --git a/frontend/src/ManageSSO/Google.jsx b/frontend/src/ManageSSO/Google.jsx index 77a0e2691e..973083c5a1 100644 --- a/frontend/src/ManageSSO/Google.jsx +++ b/frontend/src/ManageSSO/Google.jsx @@ -3,6 +3,8 @@ import { organizationService } from '@/_services'; import { toast } from 'react-hot-toast'; import { copyToClipboard } from '@/_helpers/appUtils'; import { useTranslation } from 'react-i18next'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; export function Google({ settings, updateData }) { const [enabled, setEnabled] = useState(settings?.enabled || false); @@ -61,17 +63,9 @@ export function Google({ settings, updateData }) { }; return ( -
    +
    -
    - {t('header.organization.menus.manageSSO.google.title', 'Google')} - - {enabled - ? t('header.organization.menus.manageSSO.google.enabled', 'Enabled') - : t('header.organization.menus.manageSSO.google.disabled', 'Disabled')} - -
    + +
    + + {enabled + ? t('header.organization.menus.manageSSO.google.enabled', 'Enabled') + : t('header.organization.menus.manageSSO.google.disabled', 'Disabled')} + +
    -
    +
    -
    +
    {t('header.organization.menus.manageSSO.google.redirectUrl', 'Redirect URL')} -
    +

    {`${window.public_config?.TOOLJET_HOST}/sso/google/${configId}`}

    - copyFunction('redirect-url')} - src={`assets/images/icons/copy-dark.svg`} - width="22" - height="22" - className="sso-copy" - /> + copyFunction('redirect-url')} />
    )} -
    - - -
    +
    + + {t('globals.cancel', 'Cancel')} + + + + {t('globals.savechanges', 'Save changes')} + +
    ); } diff --git a/frontend/src/ManageSSO/Loader.jsx b/frontend/src/ManageSSO/Loader.jsx index da9fb268c5..d5177ee0ef 100644 --- a/frontend/src/ManageSSO/Loader.jsx +++ b/frontend/src/ManageSSO/Loader.jsx @@ -2,7 +2,7 @@ import React from 'react'; export function Loader() { return ( -
    +
    diff --git a/frontend/src/ManageSSO/ManageSSO.jsx b/frontend/src/ManageSSO/ManageSSO.jsx index 1b4e180755..889403e8dd 100644 --- a/frontend/src/ManageSSO/ManageSSO.jsx +++ b/frontend/src/ManageSSO/ManageSSO.jsx @@ -1,24 +1,20 @@ import React, { useState, useCallback, useEffect } from 'react'; import { organizationService } from '@/_services'; -import { Menu } from '@/_components'; 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'; +import FolderList from '@/_ui/FolderList/FolderList'; export function ManageSSO({ darkMode }) { const menuItems = [ { id: 'general-settings', label: 'General Settings' }, { id: 'google', label: 'Google' }, { id: 'git', label: 'GitHub' }, - { id: 'form', label: 'Password Login' }, ]; - const { t } = useTranslation(); const changePage = useCallback( (page) => { setCurrentPage(page); @@ -33,19 +29,18 @@ export function ManageSSO({ darkMode }) { const showPage = () => { switch (currentPage) { case 'general-settings': - return ; + return ( + + ); case 'google': return obj.sso === 'google')} />; case 'git': return obj.sso === 'git')} />; - case 'form': - return ( -
    obj.sso === 'form')} - darkMode={darkMode} - /> - ); default: return ; } @@ -101,27 +96,27 @@ export function ManageSSO({ darkMode }) {
    -
    -
    -
    -
    -

    - {t('header.organization.menus.manageSSO.manageSso', 'SSO')} -

    +
    +
    +
    +
      + {menuItems.map((item, index) => { + return ( + changePage(item.id)} + key={index} + selectedItem={currentPage == item.id} + items={menuItems} + onChange={changePage} + isLoading={isLoading} + > + {item.label} + + ); + })} +
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    {showPage()}
    +
    {showPage()}
    diff --git a/frontend/src/MarketplacePage/InstalledPlugins.jsx b/frontend/src/MarketplacePage/InstalledPlugins.jsx index 3efef03ec3..1066c8552c 100644 --- a/frontend/src/MarketplacePage/InstalledPlugins.jsx +++ b/frontend/src/MarketplacePage/InstalledPlugins.jsx @@ -120,7 +120,7 @@ const InstalledPluginCard = ({ plugin, marketplacePlugin, fetchPlugins, isDevMod darkMode={darkMode} />
    -
    +
    diff --git a/frontend/src/MarketplacePage/MarketplaceCard.jsx b/frontend/src/MarketplacePage/MarketplaceCard.jsx index cbfc48d3f4..5a6146cdde 100644 --- a/frontend/src/MarketplacePage/MarketplaceCard.jsx +++ b/frontend/src/MarketplacePage/MarketplaceCard.jsx @@ -44,11 +44,11 @@ export const MarketplaceCard = ({ id, name, repo, description, version, isInstal return (
    -
    +
    - +
    @@ -63,7 +63,7 @@ export const MarketplaceCard = ({ id, name, repo, description, version, isInstal v{version}
    -
    Install{installed && 'ed'}
    +
    Install{installed && 'ed'}
    diff --git a/frontend/src/MarketplacePage/index.jsx b/frontend/src/MarketplacePage/index.jsx index 510f177dd4..25a66817a4 100644 --- a/frontend/src/MarketplacePage/index.jsx +++ b/frontend/src/MarketplacePage/index.jsx @@ -1,18 +1,20 @@ -import React from 'react'; +import React, { useContext } from 'react'; import Layout from '@/_ui/Layout'; -import { ListGroupItem } from './ListGroupItem'; import { InstalledPlugins } from './InstalledPlugins'; import { MarketplacePlugins } from './MarketplacePlugins'; import { marketplaceService, pluginsService, authenticationService } from '@/_services'; import { toast } from 'react-hot-toast'; import { useNavigate } from 'react-router-dom'; import config from 'config'; +import { BreadCrumbContext } from '@/App/App'; +import FolderList from '@/_ui/FolderList/FolderList'; const MarketplacePage = ({ darkMode, switchDarkMode }) => { const [active, setActive] = React.useState('installed'); const [marketplacePlugins, setMarketplacePlugins] = React.useState([]); const [installedPlugins, setInstalledPlugins] = React.useState([]); const [fetchingInstalledPlugins, setFetching] = React.useState(false); + const { updateSidebarNAV } = useContext(BreadCrumbContext); const { admin } = authenticationService.currentSessionValue; const ENABLE_MARKETPLACE_DEV_MODE = config.ENABLE_MARKETPLACE_DEV_MODE == 'true'; @@ -20,6 +22,8 @@ const MarketplacePage = ({ darkMode, switchDarkMode }) => { const navigate = useNavigate(); React.useEffect(() => { + updateSidebarNAV(''); + if (!admin) { navigate('/'); } @@ -55,26 +59,37 @@ const MarketplacePage = ({ darkMode, switchDarkMode }) => { setInstalledPlugins(data); }; + const itemRender = (key) => { + switch (key) { + case 'Marketplace': + return 'marketplace'; + case 'Installed': + return 'installed'; + default: + break; + } + }; + return (
    -
    +
    -
    +
    Plugins
    - setActive('installed')} - text="Installed" - /> - setActive('marketplace')} - text="Marketplace" - /> + {['Installed', 'Marketplace'].map((item, index) => ( + setActive(itemRender(item))} + > + {item} + + ))}
    {active === 'installed' ? ( diff --git a/frontend/src/OrganizationSettingsPage/index.jsx b/frontend/src/OrganizationSettingsPage/index.jsx index fabd713719..5354ea9432 100644 --- a/frontend/src/OrganizationSettingsPage/index.jsx +++ b/frontend/src/OrganizationSettingsPage/index.jsx @@ -1,138 +1,82 @@ -import React, { useState, useEffect } from 'react'; +import React, { useEffect, useState, useContext } from 'react'; import cx from 'classnames'; -import { useTranslation } from 'react-i18next'; import Layout from '@/_ui/Layout'; import { ManageOrgUsers } from '@/ManageOrgUsers'; import { ManageGroupPermissions } from '@/ManageGroupPermissions'; import { ManageSSO } from '@/ManageSSO'; import { ManageOrgVars } from '@/ManageOrgVars'; import { authenticationService } from '@/_services'; +import { BreadCrumbContext } from '../App/App'; +import FolderList from '@/_ui/FolderList/FolderList'; +import { OrganizationList } from '../_components/OrganizationManager/List'; export function OrganizationSettings(props) { const [admin, setAdmin] = useState(authenticationService.currentSessionValue?.admin); - const [selectedTab, setSelectedTab] = useState(admin ? 'users' : 'manageEnvVars'); - const { t } = useTranslation(); + const [selectedTab, setSelectedTab] = useState(admin ? 'Users & permissions' : 'manageEnvVars'); + const { updateSidebarNAV } = useContext(BreadCrumbContext); + + useEffect(() => { + updateSidebarNAV('Users & permissions'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const sideBarNavs = ['Users', 'Groups', 'SSO', 'Workspace variables']; + const defaultOrgName = (groupName) => { + switch (groupName) { + case 'Users': + return 'Users & permissions'; + case 'Groups': + return 'manageGroups'; + case 'SSO': + return 'manageSSO'; + case 'Workspace variables': + return 'manageEnvVars'; + default: + return groupName; + } + }; useEffect(() => { const subscription = authenticationService.currentSession.subscribe((newOrd) => { setAdmin(newOrd?.admin); - admin ? setSelectedTab('users') : setSelectedTab('manageEnvVars'); + admin ? setSelectedTab('Users & permissions') : setSelectedTab('manageEnvVars'); }); () => subscription.unsubsciption(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [authenticationService.currentSessionValue?.admin]); - const selectedClassName = props.darkMode ? 'bg-dark-indigo' : 'bg-light-indigo'; return (
    -
    -
    - {admin && ( - <> -
    setSelectedTab('users')} - data-cy="manage-users-option" - > - - - - -  {t('header.organization.menus.menusList.manageUsers', 'Users')} -
    -
    setSelectedTab('manageGroups')} - data-cy="manage-groups-option" - > - - - -  {t('header.organization.menus.menusList.manageGroups', 'Manage Groups')} -
    -
    setSelectedTab('manageSSO')} - data-cy="manage-sso-option" - > - - - -  {t('header.organization.menus.menusList.manageSso', 'SSO')} -
    - - )} -
    setSelectedTab('manageEnvVars')} - data-cy="workspace-variable-option" - > - - - -  {t('header.organization.menus.menusList.manageEnv', 'Manage Environment Variables')} -
    +
    +
    + {sideBarNavs.map((item, index) => { + return ( + <> + { + setSelectedTab(defaultOrgName(item)); + if (item == 'Users') updateSidebarNAV('Users & permissions'); + else updateSidebarNAV(item); + }} + selectedItem={selectedTab == defaultOrgName(item)} + dataCy={item.toLowerCase().replace(/\s+/g, '-')} + > + {item} + + + ); + })}
    +
    -
    -
    - {selectedTab === 'users' && } + +
    +
    + {selectedTab === 'Users & permissions' && } {selectedTab === 'manageGroups' && } {selectedTab === 'manageSSO' && } {selectedTab === 'manageEnvVars' && } diff --git a/frontend/src/SettingsPage/SettingsPage.jsx b/frontend/src/SettingsPage/SettingsPage.jsx index d880d88e36..3d3a7bded1 100644 --- a/frontend/src/SettingsPage/SettingsPage.jsx +++ b/frontend/src/SettingsPage/SettingsPage.jsx @@ -1,8 +1,10 @@ -import React from 'react'; +import React, { useContext, useEffect } from 'react'; import { authenticationService, userService } from '@/_services'; import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import Layout from '@/_ui/Layout'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import { BreadCrumbContext } from '@/App/App'; function SettingsPage(props) { const currentSession = authenticationService.currentSessionValue; @@ -17,6 +19,12 @@ function SettingsPage(props) { const [selectedFile, setSelectedFile] = React.useState(null); const focusRef = React.useRef(null); const { t } = useTranslation(); + const { updateSidebarNAV } = useContext(BreadCrumbContext); + + useEffect(() => { + updateSidebarNAV(''); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const updateDetails = async () => { const firstNameMatch = firstName.match(/^ *$/); @@ -120,23 +128,10 @@ function SettingsPage(props) { return (
    -
    -
    -
    -
    -
    -
    -

    - {t('header.profileSettingPage.profileSettings', 'Profile Settings')} -

    -
    -
    -
    -
    - -
    +
    +
    -
    +

    {t('header.profileSettingPage.profile', 'Profile')} @@ -145,7 +140,7 @@ function SettingsPage(props) {
    -
    +
    @@ -162,7 +157,7 @@ function SettingsPage(props) {
    -
    +
    @@ -181,7 +176,7 @@ function SettingsPage(props) {
    -
    +
    @@ -198,10 +193,10 @@ function SettingsPage(props) {
    -
    -
    +
    +
    + { const file = e.target.files[0]; @@ -220,20 +215,16 @@ function SettingsPage(props) {
    - + {/* An !important style on theme.scss is making the last child of every .card-body color to #c3c3c3!. */} {/* The div below is a placeholder to prevent it from affecting the button above. */}

    -
    +

    {t('header.profileSettingPage.changePassword', 'Change password')} @@ -242,7 +233,7 @@ function SettingsPage(props) {
    -
    +
    @@ -258,7 +249,7 @@ function SettingsPage(props) {
    -
    +
    @@ -276,7 +267,7 @@ function SettingsPage(props) {
    -
    +
    @@ -293,14 +284,14 @@ function SettingsPage(props) { />
    - + {/* An !important style on theme.scss is making the last child of every .card-body color to #c3c3c3!. */} {/* The div below is a placeholder to prevent it from affecting the button above. */}
    diff --git a/frontend/src/TooljetDatabase/Drawers/CreateColumnDrawer/index.jsx b/frontend/src/TooljetDatabase/Drawers/CreateColumnDrawer/index.jsx index d7022c0c00..8bdca77660 100644 --- a/frontend/src/TooljetDatabase/Drawers/CreateColumnDrawer/index.jsx +++ b/frontend/src/TooljetDatabase/Drawers/CreateColumnDrawer/index.jsx @@ -1,31 +1,27 @@ -import React, { useState, useContext } from 'react'; +import React, { useContext } from 'react'; import Drawer from '@/_ui/Drawer'; import { toast } from 'react-hot-toast'; import CreateColumnForm from '../../Forms/ColumnForm'; import { TooljetDatabaseContext } from '../../index'; import { tooljetDatabaseService } from '@/_services'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; -const CreateColumnDrawer = () => { +const CreateColumnDrawer = ({ setIsCreateColumnDrawerOpen, isCreateColumnDrawerOpen }) => { const { organizationId, selectedTable, setColumns, setSelectedTableData } = useContext(TooljetDatabaseContext); - const [isCreateColumnDrawerOpen, setIsCreateColumnDrawerOpen] = useState(false); return ( <> + setIsCreateColumnDrawerOpen(false)} position="right"> { diff --git a/frontend/src/TooljetDatabase/Drawers/CreateRowDrawer/index.jsx b/frontend/src/TooljetDatabase/Drawers/CreateRowDrawer/index.jsx index b3670d0087..a5e4c017b4 100644 --- a/frontend/src/TooljetDatabase/Drawers/CreateRowDrawer/index.jsx +++ b/frontend/src/TooljetDatabase/Drawers/CreateRowDrawer/index.jsx @@ -4,6 +4,7 @@ import { toast } from 'react-hot-toast'; import CreateRowForm from '../../Forms/RowForm'; import { TooljetDatabaseContext } from '../../index'; import { tooljetDatabaseService } from '@/_services'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; const CreateRowDrawer = ({ isCreateRowDrawerOpen, setIsCreateRowDrawerOpen }) => { const { organizationId, selectedTable, setSelectedTableData, setTotalRecords } = useContext(TooljetDatabaseContext); @@ -12,21 +13,10 @@ const CreateRowDrawer = ({ isCreateRowDrawerOpen, setIsCreateRowDrawerOpen }) => <> setIsCreateRowDrawerOpen(false)} position="right"> - +
    + setIsCreateTableDrawerOpen(!isCreateTableDrawerOpen)} + className="create-new-table-btn" + data-cy="add-table-button" + > + Create new table + +
    setIsCreateTableDrawerOpen(false)} position="right"> { @@ -39,6 +37,7 @@ export default function CreateTableDrawer() { if (Array.isArray(data?.result) && data.result.length > 0) { setTables(data.result || []); setSelectedTable(tableName); + updateSidebarNAV(tableName); } }); setIsCreateTableDrawerOpen(false); diff --git a/frontend/src/TooljetDatabase/Drawers/EditRowDrawer/index.jsx b/frontend/src/TooljetDatabase/Drawers/EditRowDrawer/index.jsx index 98ab2ffc2f..7a9f544ec6 100644 --- a/frontend/src/TooljetDatabase/Drawers/EditRowDrawer/index.jsx +++ b/frontend/src/TooljetDatabase/Drawers/EditRowDrawer/index.jsx @@ -12,8 +12,7 @@ const EditRowDrawer = ({ isCreateRowDrawerOpen, setIsCreateRowDrawerOpen }) => { <> diff --git a/frontend/src/TooljetDatabase/Filter/index.jsx b/frontend/src/TooljetDatabase/Filter/index.jsx index db41ba8f28..2708e3a4a9 100644 --- a/frontend/src/TooljetDatabase/Filter/index.jsx +++ b/frontend/src/TooljetDatabase/Filter/index.jsx @@ -6,6 +6,7 @@ import { FilterForm } from '../Forms/FilterForm'; import { isEmpty } from 'lodash'; import { pluralize } from '@/_helpers/utils'; import { useMounted } from '@/_hooks/use-mount'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; const Filter = ({ filters, setFilters, handleBuildFilterQuery, resetFilterQuery }) => { const [show, setShow] = useState(false); @@ -14,7 +15,7 @@ const Filter = ({ filters, setFilters, handleBuildFilterQuery, resetFilterQuery const isMounted = useMounted(); const popover = ( - +
    {Object.values(filters).map((filter, index) => { @@ -84,15 +85,14 @@ const Filter = ({ filters, setFilters, handleBuildFilterQuery, resetFilterQuery placement="bottom" overlay={popover} > -
    */} -
    +
    -
    +
    Delete
    diff --git a/frontend/src/TooljetDatabase/Table/Footer.jsx b/frontend/src/TooljetDatabase/Table/Footer.jsx index 65d9b74fb6..2d977d953f 100644 --- a/frontend/src/TooljetDatabase/Table/Footer.jsx +++ b/frontend/src/TooljetDatabase/Table/Footer.jsx @@ -86,7 +86,7 @@ const Footer = ({ darkMode, openCreateRowDrawer, dataLoading, tableDataLength }) size="sm" styles={{ width: '118px', fontSize: '12px', fontWeight: 700, borderColor: darkMode && 'transparent' }} > - +
    {tableDataLength > 0 && ( @@ -104,7 +104,7 @@ const Footer = ({ darkMode, openCreateRowDrawer, dataLoading, tableDataLength })

    { > {headerGroups.map((headerGroup, index) => ( - + {headerGroup.headers.map((column, index) => ( { ))} ))} + 0 && !darkMode, @@ -266,29 +287,33 @@ const Table = ({ openCreateRowDrawer }) => { rows.map((row, index) => { prepareRow(row); return ( - - {row.cells.map((cell, index) => { - console.log('----qa-checking----', cell); - const dataCy = - cell.column.id === 'selection' - ? `${cell.row.values?.id}-checkbox` - : `id-${cell.row.values?.id}-column-${cell.column.id}`; - return ( - - ); - })} - + <> + + {row.cells.map((cell, index) => { + const dataCy = + cell.column.id === 'selection' + ? `${cell.row.values?.id}-checkbox` + : `id-${cell.row.values?.id}-column-${cell.column.id}`; + return ( + + ); + })} + + ); }) )} +
    {column.render('Header')} + + {checkDataType(column?.dataType)} +
    - {isBoolean(cell?.value) ? cell?.value?.toString() : cell.render('Cell')} -
    + {isBoolean(cell?.value) ? cell?.value?.toString() : cell.render('Cell')} +
    diff --git a/frontend/src/TooljetDatabase/TableList/index.jsx b/frontend/src/TooljetDatabase/TableList/index.jsx index 72cd4724de..21ed8440f3 100644 --- a/frontend/src/TooljetDatabase/TableList/index.jsx +++ b/frontend/src/TooljetDatabase/TableList/index.jsx @@ -5,11 +5,17 @@ import { isEmpty } from 'lodash'; import { TooljetDatabaseContext } from '../index'; import { tooljetDatabaseService } from '@/_services'; import { ListItem } from '../TableListItem'; +import { BreadCrumbContext } from '../../App/App'; +import Search from '../Search'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; const List = () => { const { organizationId, tables, searchParam, selectedTable, setTables, setSelectedTable } = useContext(TooljetDatabaseContext); const [loading, setLoading] = useState(false); + const [showInput, setShowInput] = useState(false); + const { updateSidebarNAV } = useContext(BreadCrumbContext); + const darkMode = localStorage.getItem('darkMode') === 'true'; async function fetchTables() { setLoading(true); @@ -24,6 +30,7 @@ const List = () => { if (Array.isArray(data?.result)) { setTables(data.result || []); setSelectedTable(data?.result[0]?.table_name); + updateSidebarNAV(data?.result[0]?.table_name); } } @@ -40,8 +47,33 @@ const List = () => { return ( <> -
    - All tables ({filteredTables.length}) +
    + {!showInput ? ( + <> + All tables ({filteredTables.length}) + +
    { + setShowInput(true); + }} + data-cy="create-new-folder-button" + > + +
    + + ) : ( + setShowInput(false)} + customClass="tj-common-search-input" + autoFocus={true} + /> + )}
    {loading && } @@ -54,6 +86,7 @@ const List = () => { onDeleteCallback={fetchTables} onClick={() => { setSelectedTable(table_name); + updateSidebarNAV(table_name); }} /> ))} diff --git a/frontend/src/TooljetDatabase/TableListItem/ActionsPopover/index.jsx b/frontend/src/TooljetDatabase/TableListItem/ActionsPopover/index.jsx index 229bb00f6b..7176b07786 100644 --- a/frontend/src/TooljetDatabase/TableListItem/ActionsPopover/index.jsx +++ b/frontend/src/TooljetDatabase/TableListItem/ActionsPopover/index.jsx @@ -11,7 +11,7 @@ export const ListItemPopover = ({ onEdit, onDelete, darkMode }) => { const [open, setOpen] = React.useState(false); const popover = ( - +
    @@ -48,7 +48,7 @@ export const ListItemPopover = ({ onEdit, onDelete, darkMode }) => { return (
    { onClick={onClick} > - + {text} diff --git a/frontend/src/TooljetDatabase/TooljetDatabasePage/index.jsx b/frontend/src/TooljetDatabase/TooljetDatabasePage/index.jsx index c52fdb65b9..baeda46249 100644 --- a/frontend/src/TooljetDatabase/TooljetDatabasePage/index.jsx +++ b/frontend/src/TooljetDatabase/TooljetDatabasePage/index.jsx @@ -24,9 +24,9 @@ const TooljetDatabasePage = ({ totalTables }) => { setSortFilters, } = useContext(TooljetDatabaseContext); - const darkMode = localStorage.getItem('darkMode') === 'true'; const [isCreateRowDrawerOpen, setIsCreateRowDrawerOpen] = useState(false); const [isEditRowDrawerOpen, setIsEditRowDrawerOpen] = useState(false); + const [isCreateColumnDrawerOpen, setIsCreateColumnDrawerOpen] = useState(false); const EmptyState = () => { return ( @@ -54,29 +54,20 @@ const TooljetDatabasePage = ({ totalTables }) => { return (
    -
    +
    {totalTables === 0 && } {selectedTable && ( <>
    -
    - - {selectedTable} - -
    -
    -
    +
    +
    - + {columns?.length > 0 && ( <> {
    - setIsCreateRowDrawerOpen(true)} /> +
    setIsCreateRowDrawerOpen(true)} + openCreateColumnDrawer={() => setIsCreateColumnDrawerOpen(true)} + /> )} diff --git a/frontend/src/TooljetDatabase/index.jsx b/frontend/src/TooljetDatabase/index.jsx index 03fff22782..06b60402ba 100644 --- a/frontend/src/TooljetDatabase/index.jsx +++ b/frontend/src/TooljetDatabase/index.jsx @@ -1,8 +1,9 @@ -import React, { createContext, useState, useMemo } from 'react'; +import React, { createContext, useState, useMemo, useEffect, useContext } from 'react'; import Layout from '@/_ui/Layout'; import TooljetDatabasePage from './TooljetDatabasePage'; import { usePostgrestQueryBuilder } from './usePostgrestQueryBuilder'; import { authenticationService } from '../_services/authentication.service'; +import { BreadCrumbContext } from '@/App/App'; export const TooljetDatabaseContext = createContext({ organizationId: null, @@ -100,6 +101,13 @@ export const TooljetDatabase = (props) => { ] ); + const { updateSidebarNAV } = useContext(BreadCrumbContext); + + useEffect(() => { + updateSidebarNAV(''); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return (
    diff --git a/frontend/src/_components/AppButton.jsx b/frontend/src/_components/AppButton.jsx index 83469d1b3a..5d7fbb3dbf 100644 --- a/frontend/src/_components/AppButton.jsx +++ b/frontend/src/_components/AppButton.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import IconEl from '@/_ui/Icon/Icon'; export const ButtonBase = function ButtonBase(props) { const mapBaseSize = { @@ -12,6 +13,9 @@ export const ButtonBase = function ButtonBase(props) { as = 'button', // render it as a button or an anchor. children, disabled, + rightIcon, + leftIcon, + Icon, ...restProps } = props; @@ -19,7 +23,9 @@ export const ButtonBase = function ButtonBase(props) { return ( + {leftIcon && leftIcon} {children} + {rightIcon && rightIcon} ); }; @@ -44,7 +50,7 @@ export const IconButton = function IconButton(props) { return ( - {Icon} + ); }; diff --git a/frontend/src/_components/ConfirmDialog.jsx b/frontend/src/_components/ConfirmDialog.jsx index 010c640efd..b5aec4ff30 100644 --- a/frontend/src/_components/ConfirmDialog.jsx +++ b/frontend/src/_components/ConfirmDialog.jsx @@ -1,10 +1,10 @@ import React, { useState, useEffect } from 'react'; -import cx from 'classnames'; import Modal from 'react-bootstrap/Modal'; import { useTranslation } from 'react-i18next'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; export function ConfirmDialog({ show, title, message, onConfirm, onCancel, confirmButtonLoading, darkMode }) { - darkMode = darkMode ?? (localStorage.getItem('darkMode') || false); + darkMode = darkMode ?? (localStorage.getItem('darkMode') === 'true' || false); const [showModal, setShow] = useState(show); const { t } = useTranslation(); @@ -28,7 +28,7 @@ export function ConfirmDialog({ show, title, message, onConfirm, onCancel, confi size="sm" animation={false} centered={true} - contentClassName={darkMode ? 'theme-dark' : ''} + contentClassName={`confirm-dialogue-modal ${darkMode ? 'dark-theme' : ''}`} data-cy="modal-component" > {title && ( @@ -52,18 +52,21 @@ export function ConfirmDialog({ show, title, message, onConfirm, onCancel, confi )} - {message} + + {message} + - - + ); diff --git a/frontend/src/_components/DynamicForm.jsx b/frontend/src/_components/DynamicForm.jsx index ef4397846e..62cd771087 100644 --- a/frontend/src/_components/DynamicForm.jsx +++ b/frontend/src/_components/DynamicForm.jsx @@ -14,6 +14,7 @@ import Zendesk from '@/_components/Zendesk'; import ToolJetDbOperations from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/ToolJetDbOperations'; import { find, isEmpty } from 'lodash'; +import { ButtonSolid } from './AppButton'; const DynamicForm = ({ schema, @@ -308,14 +309,16 @@ const DynamicForm = ({ )} {(type === 'password' || encrypted) && selectedDataSource?.id && (
    - +
    )} {(type === 'password' || encrypted) && ( @@ -336,6 +339,7 @@ const DynamicForm = ({ {...getElementProps(obj[key])} {...computedProps[key]} data-cy={`${String(label).toLocaleLowerCase().replace(/\s+/g, '-')}-text-field`} + customWrap={true} //to be removed after whole ui is same />
    ); diff --git a/frontend/src/_components/Menu.jsx b/frontend/src/_components/Menu.jsx index fa30938956..860cf03423 100644 --- a/frontend/src/_components/Menu.jsx +++ b/frontend/src/_components/Menu.jsx @@ -4,8 +4,8 @@ import Skeleton from 'react-loading-skeleton'; export function Menu({ isLoading, onChange, items, selected }) { return ( -
    -
      +
      +
        {!isLoading && Array.isArray(items) && items.length > 0 && @@ -14,7 +14,7 @@ export function Menu({ isLoading, onChange, items, selected }) { key={item.id} onClick={() => onChange(item.id)} className={cx({ - active: selected === item.id, + 'folder-list-selected': selected === item.id, })} > {item.label} diff --git a/frontend/src/_components/MultiSelect.jsx b/frontend/src/_components/MultiSelectUser.jsx similarity index 74% rename from frontend/src/_components/MultiSelect.jsx rename to frontend/src/_components/MultiSelectUser.jsx index bd63d8ee84..a97bd95758 100644 --- a/frontend/src/_components/MultiSelect.jsx +++ b/frontend/src/_components/MultiSelectUser.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import Select from 'react-select-search'; import '@/_styles/widgets/multi-select.scss'; -function MultiSelect({ +function MultiSelectUser({ onSelect, onSearch, selectedValues = [], @@ -37,13 +37,37 @@ function MultiSelect({ [setSearchText, onSearch, selectedValues] ); + function renderCustom(props, option) { + return ( +
        +
        + { + onSelect([...selectedValues, option]); + }} + /> +
        +

        + {option?.first_name} {option?.last_name} +

        + {option?.email} +
        +
        +
        + {option?.first_name?.[0]} + {option?.last_name?.[0]} +
        +
        + ); + } + const filterOptions = useCallback( (options) => { return options?.filter((data) => !selectedValues.some((selected) => selected.value === data.value)); }, [selectedValues] ); - return (
        @@ -70,12 +94,14 @@ function MultiSelect({ } disabled={isLoading} fuzzySearch + renderOption={renderCustom} + customWrap={true} />
        ); } -MultiSelect.propTypes = { +MultiSelectUser.propTypes = { onSelect: PropTypes.func.isRequired, onReset: PropTypes.func, onSearch: PropTypes.func, @@ -86,4 +112,4 @@ MultiSelect.propTypes = { searchLabel: PropTypes.string, }; -export { MultiSelect }; +export { MultiSelectUser }; diff --git a/frontend/src/_components/NotificationCenter/index.jsx b/frontend/src/_components/NotificationCenter/index.jsx index 7203e1b86e..fc5e720953 100644 --- a/frontend/src/_components/NotificationCenter/index.jsx +++ b/frontend/src/_components/NotificationCenter/index.jsx @@ -6,11 +6,13 @@ import Spinner from '@/_ui/Spinner'; import { useTranslation } from 'react-i18next'; import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; import { ToolTip } from '@/_components/ToolTip'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; export const NotificationCenter = ({ darkMode }) => { const [loading, setLoading] = React.useState(false); const [isRead, setIsRead] = React.useState(false); const [commentNotifications, setCommentNotifications] = React.useState([]); + const { t } = useTranslation(); async function fetchData() { setLoading(true); @@ -42,11 +44,11 @@ export const NotificationCenter = ({ darkMode }) => { const overlay = (
        -
        +

        {t('header.notificationCenter.notifications', 'Notifications')} @@ -112,27 +114,13 @@ export const NotificationCenter = ({ darkMode }) => { return ( -
        +
        - - - +
        + + {commentNotifications?.length !== 0 && } +
        - {commentNotifications?.length !== 0 && }
        ); diff --git a/frontend/src/_components/OrganizationManager/CreateOrganization.jsx b/frontend/src/_components/OrganizationManager/CreateOrganization.jsx index a73f4afeb9..e59ac47150 100644 --- a/frontend/src/_components/OrganizationManager/CreateOrganization.jsx +++ b/frontend/src/_components/OrganizationManager/CreateOrganization.jsx @@ -3,6 +3,7 @@ import { organizationService } from '@/_services'; import AlertDialog from '@/_ui/AlertDialog'; import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import { appendWorkspaceId } from '../../_helpers/utils'; export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { @@ -42,7 +43,7 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { title={t('header.organization.createWorkspace', 'Create workspace')} >
        -
        +
        setNewOrgName(e.target.value)} @@ -51,22 +52,23 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { disabled={isCreating} maxLength={25} data-cy="workspace-name-input-field" + autoFocus />
        -
        - - +
        diff --git a/frontend/src/_components/OrganizationManager/CustomSelect.jsx b/frontend/src/_components/OrganizationManager/CustomSelect.jsx index 4a82f16049..42855ff440 100644 --- a/frontend/src/_components/OrganizationManager/CustomSelect.jsx +++ b/frontend/src/_components/OrganizationManager/CustomSelect.jsx @@ -5,60 +5,50 @@ import { EditOrganization } from './EditOrganization'; import { CreateOrganization } from './CreateOrganization'; import { useTranslation } from 'react-i18next'; import { authenticationService } from '@/_services'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; const Menu = (props) => { const { t } = useTranslation(); const { admin } = authenticationService.currentSessionValue; - + const darkMode = localStorage.getItem('darkMode') === 'true'; return ( -
        +
        {admin && ( <> -
        props.selectProps.setShowEditOrg(true)}> +
        props.selectProps.setShowEditOrg(true)} + >
        {props?.selectProps?.value?.label}
        -
        - - - - +
        +
        -
        )} -
        {props.children}
        +
        {props.children}
        - - - + - - - {t('header.organization.addNewWorkSpace', 'Add new workspace')} - +
        + +
        + +
        + {t('header.organization.addNewWorkSpace', 'Add new workspace')}
        @@ -68,7 +58,9 @@ const Menu = (props) => { const SingleValue = ({ selectProps }) => { return (
        -
        {selectProps.value.name}
        +
        + {selectProps.value.name} +
        ); }; @@ -76,6 +68,7 @@ const SingleValue = ({ selectProps }) => { export const CustomSelect = ({ ...props }) => { const [showEditOrg, setShowEditOrg] = useState(false); const [showCreateOrg, setShowCreateOrg] = useState(false); + const darkMode = localStorage.getItem('darkMode') === 'true'; return ( <> @@ -83,13 +76,15 @@ export const CustomSelect = ({ ...props }) => { setNewOrgName(e.target.value)} @@ -45,17 +47,18 @@ export const EditOrganization = ({ showEditOrg, setShowEditOrg }) => { disabled={isCreating} value={newOrgName} maxLength={25} + autoFocus />
        -
        - - +
        diff --git a/frontend/src/_components/OrganizationManager/List.jsx b/frontend/src/_components/OrganizationManager/List.jsx index 282000e7b6..ddad71e49d 100644 --- a/frontend/src/_components/OrganizationManager/List.jsx +++ b/frontend/src/_components/OrganizationManager/List.jsx @@ -7,6 +7,7 @@ export const OrganizationList = function () { const { current_organization_id } = authenticationService.currentSessionValue; const [organizationList, setOrganizationList] = useState([]); const [getOrgStatus, setGetOrgStatus] = useState(''); + const darkMode = localStorage.getItem('darkMode') === 'true'; useEffect(() => { setGetOrgStatus('loading'); @@ -30,25 +31,24 @@ export const OrganizationList = function () { value: org.id, name: org.name, label: ( -
        -
        - - {getAvatar(org.name)} - -
        -
        -
        {org.name}
        +
        +
        + {getAvatar(org.name)}
        +
        {org.name}
        ), })); return ( - switchOrganization(id)} - /> +
        + switchOrganization(id)} + className={`tj-org-select ${darkMode && 'dark-theme'}`} + /> +
        ); }; diff --git a/frontend/src/_components/Pagination.jsx b/frontend/src/_components/Pagination.jsx index a500e513a3..7dc3c40169 100644 --- a/frontend/src/_components/Pagination.jsx +++ b/frontend/src/_components/Pagination.jsx @@ -56,11 +56,15 @@ export const Pagination = function Pagination({ currentPage, count, pageChanged, } return ( -
        +

        {t('homePage.pagination.showing', 'Showing')} {startingAppCount()}{' '} {t('homePage.pagination.to', 'to')} {endingAppCount()} {t('homePage.pagination.of', 'of')}{' '} - {count} + {count}

        • diff --git a/frontend/src/_components/Profile.jsx b/frontend/src/_components/Profile.jsx index 6bc11ba415..8163c925c6 100644 --- a/frontend/src/_components/Profile.jsx +++ b/frontend/src/_components/Profile.jsx @@ -6,8 +6,9 @@ import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; import { useTranslation } from 'react-i18next'; import { ToolTip } from '@/_components/ToolTip'; import { getPrivateRoute } from '@/_helpers/routes'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; -export const Profile = function Header({ switchDarkMode, darkMode }) { +export const Profile = function Header({ darkMode }) { const currentSession = authenticationService.currentSessionValue; const [currentUser, setCurrentUser] = React.useState({ first_name: currentSession?.current_user.first_name, @@ -40,84 +41,28 @@ export const Profile = function Header({ switchDarkMode, darkMode }) { const getOverlay = () => { return ( -
          +
          - - - - + - {t('header.profile', 'Profile')} + {t('header.profile', 'Profile')} -
          switchDarkMode(!darkMode)} - data-cy="mode-switch-button" - > - - - - {darkMode ? 'Light Mode' : 'Dark Mode'} -
          - - - + - {t('header.logout', 'Logout')} + {t('header.logout', 'Logout')}
          ); diff --git a/frontend/src/_components/SearchBox.jsx b/frontend/src/_components/SearchBox.jsx index c539f46330..0a4521c0a7 100644 --- a/frontend/src/_components/SearchBox.jsx +++ b/frontend/src/_components/SearchBox.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import cx from 'classnames'; import useDebounce from '@/_hooks/useDebounce'; import { useMounted } from '@/_hooks/use-mount'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; export function SearchBox({ width = '200px', @@ -13,6 +14,9 @@ export function SearchBox({ placeholder = 'Search', customClass = '', dataCy = '', + callBack, + onClearCallback, + autoFocus = false, }) { const [searchText, setSearchText] = useState(''); const debouncedSearchTerm = useDebounce(searchText, debounceDelay); @@ -20,34 +24,29 @@ export function SearchBox({ const handleChange = (e) => { setSearchText(e.target.value); + callBack?.(e); }; const clearSearchText = () => { setSearchText(''); + onClearCallback?.(); }; const mounted = useMounted(); useEffect(() => { if (mounted) { - onSubmit(debouncedSearchTerm); + onSubmit?.(debouncedSearchTerm); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [debouncedSearchTerm, onSubmit]); return (
          -
          +
          {!isFocused && ( - - - + )} setFocussed(true)} onBlur={() => setFocussed(false)} data-cy={`${dataCy}-search-bar`} + autoFocus={autoFocus} /> - {isFocused && searchText && ( + {isFocused && ( -
          - - - - - +
          +
          )} diff --git a/frontend/src/_components/index.js b/frontend/src/_components/index.js index 61c5573306..9eee8a94bb 100644 --- a/frontend/src/_components/index.js +++ b/frontend/src/_components/index.js @@ -9,4 +9,4 @@ export * from './Menu'; export * from './LoginLoader'; export * from './RedirectLoader'; export * from './FilterPreview'; -export * from './MultiSelect'; +export * from './MultiSelectUser'; diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index 959ebb3b03..2629f15423 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -26,7 +26,6 @@ import urlJoin from 'url-join'; import { tooljetDbOperations } from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/operations'; import { authenticationService } from '@/_services/authentication.service'; import { setCookie } from '@/_helpers/cookie'; -import { flushSync } from 'react-dom'; // TODO: It can be removed once we've a proper state update flow const ERROR_TYPES = Object.freeze({ ReferenceError: 'ReferenceError', @@ -345,8 +344,7 @@ function showModal(_ref, modal, show) { function logoutAction(_ref) { localStorage.clear(); - _ref.props.navigate('/login'); - window.location.href = '/login'; + authenticationService.logout(true); return Promise.resolve(); } @@ -752,6 +750,7 @@ export async function onEvent(_ref, eventName, options, mode = 'edit') { 'onRowHovered', 'onSubmit', 'onInvalid', + 'onNewRowsAdded', ].includes(eventName) ) { const { component } = options; @@ -966,51 +965,48 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode = if (promiseStatus === 'failed' || promiseStatus === 'Bad Request') { const errorData = query.kind === 'runpy' ? data.data : data; - flushSync(() => { - return _self.setState( - { - currentState: { - ..._self.state.currentState, - queries: { - ..._self.state.currentState.queries, - [queryName]: _.assign( - { - ..._self.state.currentState.queries[queryName], - isLoading: false, - }, - query.kind === 'restapi' - ? { - request: data.data.requestObject, - response: data.data.responseObject, - responseHeaders: data.data.responseHeaders, - } - : {} - ), - }, - errors: { - ..._self.state.currentState.errors, - [queryName]: { - type: 'query', - kind: query.kind, - data: errorData, - options: options, + return _self.setState( + { + currentState: { + ..._self.state.currentState, + queries: { + ..._self.state.currentState.queries, + [queryName]: _.assign( + { + ..._self.state.currentState.queries[queryName], + isLoading: false, }, + query.kind === 'restapi' + ? { + request: data.data.requestObject, + response: data.data.responseObject, + responseHeaders: data.data.responseHeaders, + } + : {} + ), + }, + errors: { + ..._self.state.currentState.errors, + [queryName]: { + type: 'query', + kind: query.kind, + data: errorData, + options: options, }, }, }, - () => { - resolve(data); - onEvent(_self, 'onDataQueryFailure', { - definition: { events: dataQuery.options.events }, - }); - if (mode !== 'view') { - const err = - query.kind == 'tooljetdb' ? data?.error || data : _.isEmpty(data.data) ? data : data.data; - toast.error(err?.message); - } + }, + () => { + resolve(data); + onEvent(_self, 'onDataQueryFailure', { + definition: { events: dataQuery.options.events }, + }); + if (mode !== 'view') { + const err = query.kind == 'tooljetdb' ? data?.error || data : _.isEmpty(data.data) ? data : data.data; + toast.error(err?.message); } - ); - }); + } + ); } else { let rawData = data.data; let finalData = data.data; @@ -1025,36 +1021,34 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode = 'edit' ); if (finalData.status === 'failed') { - flushSync(() => { - return _self.setState( - { - currentState: { - ..._self.state.currentState, - queries: { - ..._self.state.currentState.queries, - [queryName]: { - ..._self.state.currentState.queries[queryName], - isLoading: false, - }, + return _self.setState( + { + currentState: { + ..._self.state.currentState, + queries: { + ..._self.state.currentState.queries, + [queryName]: { + ..._self.state.currentState.queries[queryName], + isLoading: false, }, - errors: { - ..._self.state.currentState.errors, - [queryName]: { - type: 'transformations', - data: finalData, - options: options, - }, + }, + errors: { + ..._self.state.currentState.errors, + [queryName]: { + type: 'transformations', + data: finalData, + options: options, }, }, }, - () => { - resolve(finalData); - onEvent(_self, 'onDataQueryFailure', { - definition: { events: dataQuery.options.events }, - }); - } - ); - }); + }, + () => { + resolve(finalData); + onEvent(_self, 'onDataQueryFailure', { + definition: { events: dataQuery.options.events }, + }); + } + ); } } @@ -1064,65 +1058,61 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode = duration: notificationDuration, }); } - flushSync(() => { - _self.setState( - { - currentState: { - ..._self.state.currentState, - queries: { - ..._self.state.currentState.queries, - [queryName]: _.assign( - { - ..._self.state.currentState.queries[queryName], - isLoading: false, - data: finalData, - rawData, - }, - query.kind === 'restapi' - ? { - request: data.request, - response: data.response, - responseHeaders: data.responseHeaders, - } - : {} - ), - }, - }, - }, - () => { - resolve({ status: 'ok', data: finalData }); - onEvent(_self, 'onDataQuerySuccess', { definition: { events: dataQuery.options.events } }, mode); - - if (mode !== 'view') { - toast(`Query (${queryName}) completed.`, { - icon: '🚀', - }); - } - } - ); - }); - } - }) - .catch(({ error }) => { - if (mode !== 'view') toast.error(error ?? 'Unknown error'); - flushSync(() => { _self.setState( { currentState: { ..._self.state.currentState, queries: { ..._self.state.currentState.queries, - [queryName]: { - isLoading: false, - }, + [queryName]: _.assign( + { + ..._self.state.currentState.queries[queryName], + isLoading: false, + data: finalData, + rawData, + }, + query.kind === 'restapi' + ? { + request: data.request, + response: data.response, + responseHeaders: data.responseHeaders, + } + : {} + ), }, }, }, () => { - resolve({ status: 'failed', message: error }); + resolve({ status: 'ok', data: finalData }); + onEvent(_self, 'onDataQuerySuccess', { definition: { events: dataQuery.options.events } }, mode); + + if (mode !== 'view') { + toast(`Query (${queryName}) completed.`, { + icon: '🚀', + }); + } } ); - }); + } + }) + .catch(({ error }) => { + if (mode !== 'view') toast.error(error ?? 'Unknown error'); + _self.setState( + { + currentState: { + ..._self.state.currentState, + queries: { + ..._self.state.currentState.queries, + [queryName]: { + isLoading: false, + }, + }, + }, + }, + () => { + resolve({ status: 'failed', message: error }); + } + ); }); }); }); diff --git a/frontend/src/_helpers/routes.js b/frontend/src/_helpers/routes.js index 189335388f..28bfbb0825 100644 --- a/frontend/src/_helpers/routes.js +++ b/frontend/src/_helpers/routes.js @@ -15,7 +15,7 @@ export const getPrivateRoute = (page, params = {}) => { }; let url = routes[page]; - const urlParams = url.split('/').map((path) => { + const urlParams = url?.split('/').map((path) => { if (path.startsWith(':')) { return params[path.substring(1)]; } diff --git a/frontend/src/_services/authentication.service.js b/frontend/src/_services/authentication.service.js index 4002f9eac0..0768f02d55 100644 --- a/frontend/src/_services/authentication.service.js +++ b/frontend/src/_services/authentication.service.js @@ -21,6 +21,7 @@ const currentSessionSubject = new BehaviorSubject({ authentication_status: null, authentication_failed: null, isUserUpdated: false, + load_app: false, //key is used only in the viewer mode }); export const authenticationService = { @@ -230,7 +231,7 @@ function resetPassword(params) { return fetch(`${config.apiUrl}/reset-password`, requestOptions).then(handleResponse); } -function logout() { +function logout(avoidRedirection = false) { const requestOptions = { method: 'GET', headers: authHeader(), @@ -241,16 +242,20 @@ function logout() { .then(handleResponseWithoutValidation) .then(() => { const loginPath = (window.public_config?.SUB_PATH || '/') + 'login'; - const pathname = window.public_config?.SUB_PATH - ? window.location.pathname.replace(window.public_config?.SUB_PATH, '') - : window.location.pathname; - window.location.href = - loginPath + - `?redirectTo=${ - !pathname.includes('integrations') - ? excludeWorkspaceIdFromURL(pathname) - : `${pathname.indexOf('/') === 0 ? '' : '/'}${pathname}` - }`; + if (avoidRedirection) { + window.location.href = loginPath; + } else { + const pathname = window.public_config?.SUB_PATH + ? window.location.pathname.replace(window.public_config?.SUB_PATH, '') + : window.location.pathname; + window.location.href = + loginPath + + `?redirectTo=${ + !pathname.includes('integrations') + ? excludeWorkspaceIdFromURL(pathname) + : `${pathname.indexOf('/') === 0 ? '' : '/'}${pathname}` + }`; + } }) .catch(() => { authenticationService.updateCurrentSession({ diff --git a/frontend/src/_services/organization.service.js b/frontend/src/_services/organization.service.js index 05c89c117b..e15cc2de37 100644 --- a/frontend/src/_services/organization.service.js +++ b/frontend/src/_services/organization.service.js @@ -15,8 +15,8 @@ export const organizationService = { function getUsers(page, options) { const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' }; - const { firstName, lastName, email, status } = options; - const query = queryString.stringify({ page, firstName, lastName, email, status }); + const { firstName, lastName, email, searchText, status } = options; + const query = queryString.stringify({ page, firstName, lastName, email, status, searchText }); return fetch(`${config.apiUrl}/organizations/users?${query}`, requestOptions).then(handleResponse); } diff --git a/frontend/src/_styles/components.scss b/frontend/src/_styles/components.scss index 50b6934860..78d088b326 100644 --- a/frontend/src/_styles/components.scss +++ b/frontend/src/_styles/components.scss @@ -15,8 +15,9 @@ $btn-dark-color: #FFFFFF; font-weight: 500; cursor: pointer; transition: all 0.3s ease-in-out; + &:hover { - @if $bg != none { + @if $bg !=none { background-color: darken($bg, 10%); } } @@ -24,166 +25,171 @@ $btn-dark-color: #FFFFFF; .base-button { - @include button($btn-bg, $btn-color); - border-radius: $base-border-radius; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - padding: 4px 16px; - border: 1px solid #D7DBDF; + @include button($btn-bg, $btn-color); + border-radius: $base-border-radius; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 4px 16px; + border: 1px solid #D7DBDF; } .base-button.dark { - background: $btn-dark-bg; - color: $btn-dark-color; - border-color: #11181C; + background: $btn-dark-bg; + color: $btn-dark-color; + border-color: #11181C; - &:hover { - background: lighten($btn-dark-bg, 10%); - } + &:hover { + background: lighten($btn-dark-bg, 10%); + } - img { - filter: brightness(0) invert(1); - } + img { + filter: brightness(0) invert(1); + } } -.unstyled-button { - @include button(none, inherit); - border: none; - font-size: 12px; - font-family: 'Roboto'; - font-style: normal; - font-weight: 400; - line-height: 20px; +.unstyled-button { + @include button(none, inherit); + border: none; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 20px; } .unstyled-button.dark { - color: #FFFFFF; + color: #FFFFFF; - img { - filter: brightness(0) invert(1); - } + img { + filter: brightness(0) invert(1); + } } .page-handle-button-container { - border-radius: $base-border-radius; - display: flex; - flex-direction: row; - justify-content: left; - align-items: center; - padding: 6px 8px; - border: 1px solid #D7DBDF; - height: 32px; + border-radius: $base-border-radius; + display: flex; + flex-direction: row; + justify-content: left; + align-items: center; + padding: 6px 8px; + border: 1px solid #D7DBDF; + height: 32px; - img { - position: absolute; - right: 0 !important; - margin-right: 1.5rem !important; - filter: invert(38%) sepia(85%) saturate(5221%) hue-rotate(217deg) brightness(91%) contrast(90%); - } + img { + position: absolute; + right: 0 !important; + margin-right: 1.5rem !important; + filter: invert(38%) sepia(85%) saturate(5221%) hue-rotate(217deg) brightness(91%) contrast(90%); + } } .popover-dark-themed .page-handle-button-container { - border-color: #697177; - img { - filter: invert(95%) sepia(38%) saturate(4716%) hue-rotate(180deg) brightness(113%) contrast(102%); - } + border-color: #697177; + + img { + filter: invert(95%) sepia(38%) saturate(4716%) hue-rotate(180deg) brightness(113%) contrast(102%); + } } .leftsidebar-panel-header { - height: 100%; - background-color: #F1F3F5; - - .panel-header-container { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 16px; - height: 52px; - border-bottom: 1px solid #E6E8EB; - - .add-new-page { - margin-left: 4px; - } - - } - .panel-search-container { - padding: 8px 12px; - border-bottom: 1px solid #E6E8EB; + height: 100%; + background-color: #F1F3F5; + + .panel-header-container { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + height: 52px; + border-bottom: 1px solid #E6E8EB; + + .add-new-page { + margin-left: 4px; } + } + .panel-search-container { + padding: 8px 12px; + border-bottom: 1px solid #E6E8EB; + } +} + .leftsidebar-panel-header.dark { - // background-color: #202425; - background-color: #1F2936; - .panel-header-container, .panel-search-container { - border-color: #697177; - } + background-color: #1F2936 !important; + + .panel-header-container, + .panel-search-container { + border-color: #697177; + } } .page-selector-panel-body { - height: 100%; - padding: 12px; - background-color: #FFFFFF; + height: 100%; + padding: 12px; + background-color: #FFFFFF; - .page-handler { - height: 32px !important; - padding: 0; - margin-bottom: 6px; - font-weight: 500; - - .card, .card-body { - padding: 2px; - height: 32px; + .page-handler { + height: 32px !important; + padding: 0; + margin-bottom: 6px; + font-weight: 500; - .page-name { - padding: 2px; - } - } - - .card.active { - background: #E6EDFE; - } - - .card.non-active-page { - border: none; - box-shadow: none; - } - - .card:hover { - background: #ECEEF0; - } - - .page-name-input { - height: 32px; - } + .card, + .card-body { + padding: 2px; + height: 32px; + + .page-name { + padding: 2px; } + } + + .card.active { + background: #E6EDFE; + } + + .card.non-active-page { + border: none; + box-shadow: none; + } + + .card:hover { + background: #ECEEF0; + } + + .page-name-input { + height: 32px; + } + } } .page-selector-panel-body.dark { - background: #1F2936; + background: #1F2936 !important; - .page-handler { - .card { - background: none !important - } - .card.active { - background: #26292B !important; - } - .card:hover{ - background: #2C3547 !important; - } + .page-handler { + .card { + background: none !important } + + .card.active { + background: #26292B !important; + } + + .card:hover { + background: #2C3547 !important; + } + } } .left-sidebar-page-selector.dark { - background: #1F2936 !important; + background: #1F2936 !important; - .clear-icon { - filter: invert(100%); - } + .clear-icon { + filter: invert(100%); + } } #page-handler-menu.global-settings { @@ -192,34 +198,34 @@ $btn-dark-color: #FFFFFF; } #page-handler-menu { - border-radius: 4px; - width: 238px; - margin-top: 0.2rem !important; - margin-left: 0.5rem !important; - box-shadow: 0px 3px 2px rgba(0, 0, 0, 0.25); - - .popover-body { - padding: 16px 6px 0px 6px; - height: 100%; - - .card-body { - padding: 0; - height: 100%; - } - - .field { - font-weight: 500; - font-size: 0.7rem; + border-radius: 4px; + width: 238px; + margin-top: 0.2rem !important; + margin-left: 0.5rem !important; + box-shadow: 0px 3px 2px rgba(0, 0, 0, 0.25); - &:hover { - color:#919eab; - } - - &__danger { - color: #ff6666; - } + .popover-body { + padding: 16px 6px 0px 6px; + height: 100%; + + .card-body { + padding: 0; + height: 100%; + } + + .field { + font-weight: 500; + font-size: 0.7rem; + + &:hover { + color: #919eab; + } + + &__danger { + color: #ff6666; } } + } } .page-icons { @@ -232,51 +238,52 @@ $btn-dark-color: #FFFFFF; } .page-handler-alert { - background-color: #fff5f0!important; - border: 1px solid #FFF1E7!important; + background-color: #fff5f0 !important; + border: 1px solid #FFF1E7 !important; } .page-handle-edit-container { - height: 60px; - width: 100%; + height: 60px; + width: 100%; - .input-group { - height: 42px; - padding: 2px; - } - - .input-group-text { - border: none; - background: none; - font-weight: 400; - font-size: 14px; - line-height: 20px; - padding-right: 4px; - } + .input-group { + height: 42px; + padding: 2px; + } - .page-handler-input { - border-radius: $base-border-radius !important; - } + .input-group-text { + border: none; + background: none; + font-weight: 400; + font-size: 14px; + line-height: 20px; + padding-right: 4px; + } + + .page-handler-input { + border-radius: $base-border-radius !important; + } } .page-handle-edit-modal.theme-dark { - background: none !important; + background: none !important; - .input-group-text { - border: none !important; - background: none!important; ; - } + .input-group-text { + border: none !important; + background: none !important; + ; + } } .page-handle-tip { - text-decoration: none!important; + text-decoration: none !important; } .delete-btn.field__danger { - img { - filter: invert(37%) sepia(50%) saturate(2105%) hue-rotate(342deg) brightness(93%) contrast(93%); - } + img { + filter: invert(37%) sepia(50%) saturate(2105%) hue-rotate(342deg) brightness(93%) contrast(93%); + } } .clear-icon { diff --git a/frontend/src/_styles/custom.scss b/frontend/src/_styles/custom.scss index a3b3ebfaf2..a0503b78f9 100644 --- a/frontend/src/_styles/custom.scss +++ b/frontend/src/_styles/custom.scss @@ -3,6 +3,11 @@ margin-top: 2px; margin-right: 5px; + input.form-control:disabled{ + background-color: #ff000000; + border: 0px; + } + .rdtPicker { position: fixed; diff --git a/frontend/src/_styles/designtheme.scss b/frontend/src/_styles/designtheme.scss index 99b9c3fd5b..598722b118 100644 --- a/frontend/src/_styles/designtheme.scss +++ b/frontend/src/_styles/designtheme.scss @@ -63,9 +63,11 @@ :root { --base: white; + --base-black: #121212; } .dark-theme { /* Remap your colors for dark mode */ --base: #121212; + --base-black: white; } \ No newline at end of file diff --git a/frontend/src/_styles/drawer.scss b/frontend/src/_styles/drawer.scss index fad7a767e7..7640377da6 100644 --- a/frontend/src/_styles/drawer.scss +++ b/frontend/src/_styles/drawer.scss @@ -3,14 +3,16 @@ } .drawer { - background: #fff; - width: 40%; + background: var(--base); + width: 534px; height: 100%; overflow: auto; position: fixed; - box-shadow: 0 0 15px rgba(0, 0, 0, 0.5); + border: 1px solid var(--slate5); + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); transition: transform var(--transition-speed) ease; z-index: 1000; + background: var(--base); &.left { top: 0; @@ -44,11 +46,14 @@ } .drawer-container.in.open { - .left, .right { + + .left, + .right { transform: translateX(0); } - .top, .bottom { + .top, + .bottom { transform: translateY(0); } } @@ -73,14 +78,3 @@ pointer-events: auto; z-index: 999; } - -.theme-dark { - .drawer { - background: #22272E; - - .btn { - background-color: #273342; - color: #fff; - } - } -} \ No newline at end of file diff --git a/frontend/src/_styles/dropdown-custom.scss b/frontend/src/_styles/dropdown-custom.scss new file mode 100644 index 0000000000..fc3379b835 --- /dev/null +++ b/frontend/src/_styles/dropdown-custom.scss @@ -0,0 +1,70 @@ +// for selects and dropdowns across app dashboard +.react-select__control { + background-color: var(--base) !important; +} + +.react-select__menu { + background-color: var(--base) !important; + border: 1px solid var(--slate3) !important; + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03) !important; + margin: 0px !important; + + .react-select__menu-list { + background-color: var(--base) !important; + overflow-y: auto; + + .react-select__option { + background-color: var(--base) !important; + + &:hover { + background-color: var(--slate3) !important; + } + } + } +} + +.org-select-container { + height: 52px; + display: flex; + align-items: center; + justify-content: center; + border-top: 1px solid var(--slate3); +} + +.tj-org-select { + .react-select__control { + width: 262px; + height: 32px; + + &:hover { + background: var(--slate2) !important; + } + + &:active { + background: var(--slate3) !important; + } + } +} + +.users-filter-dropdown, +.data-type-dropdown-section, +.row-edit-select-container, +.select-operation-field, +.select-column-field, +.select-order-field, +.select-column-field { + .react-select__control { + border: 1px solid var(--slate7) !important; + } +} + +.css-1ms6gku-MenuPortal, +.css-169zxdi-MenuList { + .react-select__option { + border-radius: 6px; + } +} + +.css-nw08ma-menu { + box-shadow: none !important; +} \ No newline at end of file diff --git a/frontend/src/_styles/global-datasources.scss b/frontend/src/_styles/global-datasources.scss index 6040a4a1a6..3dfc390820 100644 --- a/frontend/src/_styles/global-datasources.scss +++ b/frontend/src/_styles/global-datasources.scss @@ -2,26 +2,47 @@ @import "./designtheme.scss"; .global-datasources-sidebar { - height: calc(100vh - 48px); + height: calc(100vh - 64px); max-width: 288px; - overflow-y: scroll; + overflow-y: auto; + background: var(--base); .add-datasource-btn { - height: 32px; + height: 40px; background: var(--indigo9); + width: 248px !important; + display: flex; + margin: 0 auto; + border-radius: 6px; } .datasources-list { display: flex; + padding: 6px 8px; + width: 248px; + height: 32px; + margin-bottom: 2px; + + &:focus-visible { + box-shadow: 0px 0px 0px 4px #DFE3E6; + outline: none; + } &:hover { - background-color: var(--indigo2); + background: var(--slate4); + border-radius: 6px; .ds-delete-btn { display: block; } } + &:active { + background: var(--indigo4); + box-shadow: none; + } + + .ds-delete-btn { display: none; border: none; @@ -66,26 +87,40 @@ .datasource-modal-container { position: relative; + background: var(--slate2); + + .modal-header { + background-color: var(--base) !important; + } .modal { position: absolute; + background: var(--slate2); } .modal-content { border: 1px solid var(--slate5); + background-color: var(--base) !important; + .input-icon { &:hover { input { padding-right: 2rem !important; } + .input-icon-addon { justify-content: flex-end; } } } + .close-btn.dark { filter: none !important; background-color: revert !important; } } } + +.datasource-inner-sidebar-wrap { + min-height: calc(100vh - 185px); + } \ No newline at end of file diff --git a/frontend/src/_styles/left-sidebar.scss b/frontend/src/_styles/left-sidebar.scss index 6da070360a..1819eedaff 100644 --- a/frontend/src/_styles/left-sidebar.scss +++ b/frontend/src/_styles/left-sidebar.scss @@ -1,11 +1,12 @@ @import "./colors.scss"; +@import "./designtheme.scss"; .left-sidebar { - background: $white; + background: var(--base) !important; .sidebar-svg-icon { &:hover { - background: #ECEEF0; + background: var(--slate5); border-radius: 6px; } } @@ -22,12 +23,14 @@ } } } + .left-sidebar-stack-bottom { width: 48px; position: fixed; bottom: 5px; text-align: center; } + .popover { position: fixed; left: 48px; @@ -35,18 +38,22 @@ overflow: auto; max-height: 60%; } + .datasources-popover { top: 160px; width: 200px; + .add-btn { border: none; margin-top: -4px; } } + .debugger-popover { top: 220px; cursor: pointer; } + .global-settings-popover { top: 260px; max-width: 350px; @@ -58,6 +65,7 @@ overflow: hidden; transition: max-height 0.25s cubic-bezier(0.5, 0, 0.1, 1); } + &.open { transition: max-height 0.25s cubic-bezier(0.5, 0, 0.1, 1); overflow: hidden; @@ -67,21 +75,26 @@ .object-key-val { margin-top: 0; } - .icon-container + span { + + .icon-container+span { margin-top: -0.14rem; - + span { + + +span { transform: translateY(-0.2rem); } } - .brace-row > span { + + .brace-row>span { transform: translateY(-0.2rem); } } } + .iopen { transform: rotate(90deg); transition: 0.12s; } + .debugger-badge { position: fixed; margin-top: -0.5rem; @@ -90,30 +103,37 @@ width: 20px; font-weight: 400; } + .zoom-popover { top: 500px; } + .sidebar-zoom { white-space: nowrap; font-size: 12px; } + .show { display: block; } + .hide { display: none; } + .dark-mode { transform: scale(0.7); } + .no-border { border: 0; } + .comment-badge { position: absolute; top: 5px; right: 5px; - transform: translate(50%,-50%); + transform: translate(50%, -50%); transform-origin: 100% 0%; } } @@ -125,6 +145,7 @@ border-radius: 4px; color: $primary; height: 36px; + &:hover { background: $primary; color: #fffffc; @@ -134,17 +155,19 @@ .dark-button { @extend .datasource-modal-button; background: transparent !important; + &:hover { - background: $primary !important; + background: $primary !important; color: #fffffc !important; } } .datasource-footer-info { - border: 1px solid #a6b6cc !important; box-sizing: border-box; - border-radius: 4px !important; - height: 68px; + height: 88px; + background: var(--slate2); + border: 1px solid var(--slate3); + border-radius: 6px; .copied { color: #7a95fb; @@ -155,6 +178,7 @@ button.copy-button { @extend .datasource-modal-button; width: 88px; + svg { margin-right: 0.5rem; } @@ -173,18 +197,30 @@ padding: 9px 12px !important; height: 34px; + &:focus-visible { + box-shadow: 0px 0px 0px 4px #DFE3E6 !important; + outline: none; + } + &:hover { - border-radius: 4px !important; - background-color: $bg-light; + background: var(--slate4) !important; + border-radius: 6px; + } + + &:active { + background: var(--indigo4) !important; + box-shadow: none; + } } .list-group-item.active { border-radius: 4px !important; - background-color: $primary !important; + background-color: var(--indigo4) !important; + color: var(--slate12); } - .list-group-item + .list-group-item.active { + .list-group-item+.list-group-item.active { margin-top: 0px !important; } } @@ -204,6 +240,7 @@ .selected-datasource-list-content { padding-left: 2rem !important; height: inherit; + .tab-content { width: 860px !important; position: absolute; @@ -222,6 +259,7 @@ svg { margin-top: 24px; } + span { margin: 12px 0 24px !important; } @@ -251,7 +289,7 @@ .modal-body-content.dark { .selected-datasource-list-content { .card:hover { - background-color: #2c405c !important; + background-color: var(--slate2) !important; } } } @@ -301,12 +339,13 @@ } .datasource-modal-sidebar-footer { - background: #283444; + background: var(--base); border-left: none; border-bottom: none; - border-color: #324156; + border-color: var(--slate5) !important; } } + .select-datasource-list-modal { .modal-body { padding: 0px !important; @@ -315,17 +354,18 @@ .modal-body { .datasource-modal-sidebar-footer { - border: 1px solid #d2ddec; + border: 1px solid var(--slate5); width: 242px !important; height: 71px; - background-color: #fffffc; - padding: 16px 24px !important; + background-color: var(--base); + padding: 16px 12px !important; position: absolute; left: 0; bottom: 0; p { margin: 0; + .footer-text { font-weight: 300; font-size: 12px; @@ -385,7 +425,8 @@ cursor: pointer; } -.close-btn.dark,.back-btn.dark { +.close-btn.dark, +.back-btn.dark { background-color: revert !important; } @@ -396,6 +437,7 @@ font-size: 18px; line-height: 22px; } + .suggestingDatasourcesWrapper { position: absolute; left: 0; @@ -410,14 +452,15 @@ .viewer-page-handler { height: 32px; padding: 0; - // max-width: 185px; - + .card { background: none; border: none; box-shadow: none; } - .card,.card-body { + + .card, + .card-body { padding: 3px; height: 32px; } @@ -447,7 +490,7 @@ .card.active { width: inherit; - background-color: #ECEEF0; + background-color: var(--slate5); color: #3e525b; } } @@ -462,6 +505,7 @@ .viewer-footer { height: 48px; } + .sidebar-comments { position: fixed !important; top: 0; @@ -469,14 +513,17 @@ border-left: 1px solid #d2ddec; padding: 5px 0 5px 5px !important; } + .sidebar-comments.dark { border-color: #324156; } + .sidebar-global-settings { position: absolute; left: 50px; top: 8px; } + .sidebar-h-100-popover { position: relative; height: 100vh; @@ -487,15 +534,104 @@ bottom: 3rem; } } + .sidebar-h-100-popover-inspector { min-width: 422px; } -.theme-dark{ - .left-sidebar{ + +.theme-dark { + .left-sidebar { .sidebar-svg-icon { &:hover { background: #2F3C4C; } } - } + } +} + +.left-sidebar { + display: flex; + flex-direction: column; + align-items: center; + padding-top: 16px; + width: 56px; + border-right: 1px solid var(--slate5); +} + +.tj-leftsidebar-icon-wrap { + display: flex; + width: 44px; + max-height: 236px; + flex-direction: column; + align-items: center; +} + + +.tj-leftsidebar-icon-items-bottom { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + position: absolute; + bottom: 0; + padding-bottom: 8px; + width: 44px; + max-height: 180px; +} + +.tj-leftsidebar-icon-items { + margin-bottom: 22px; + height: 32px; + width: 32px; + border-radius: 4px; + display: flex; + justify-content: center; + align-items: center; + + + &:hover { + background: var(--slate4); + } + + &:focus-visible { + box-shadow: 0px 0px 0px 4px #DFE3E6; + background: var(--slate4); + outline: none; + } + + + &:active { + background: var(--indigo4); + box-shadow: none; + } +} + +.application-brand { + margin-bottom: 24px !important; + +} + +.current-seleted-route { + background: #E6EDFE !important; +} + +.dark-theme { + .current-seleted-route { + background: #1C274F !important; + } +} + +.folder-info { + display: contents; + font-weight: 500 !important; + display: flex; + align-items: center; + letter-spacing: -0.02em; + text-transform: uppercase; + color: var(-—slate9) !important; + + + p { + color: var(-—slate9) !important; + } } \ No newline at end of file diff --git a/frontend/src/_styles/onboarding.scss b/frontend/src/_styles/onboarding.scss index 192f2bf2e3..e08fbb69e5 100644 --- a/frontend/src/_styles/onboarding.scss +++ b/frontend/src/_styles/onboarding.scss @@ -29,7 +29,6 @@ font-weight: 400; font-size: 14px; line-height: 20px; - font-family: 'Roboto'; text-align: center; margin-bottom: 32px; } @@ -198,7 +197,6 @@ .info-screen-description { margin-bottom: 0px !important; - font-family: 'Roboto'; font-style: normal; font-weight: 400; font-size: 14px; diff --git a/frontend/src/_styles/popover.scss b/frontend/src/_styles/popover.scss index 83936c339f..4ef58993eb 100644 --- a/frontend/src/_styles/popover.scss +++ b/frontend/src/_styles/popover.scss @@ -34,7 +34,7 @@ } .PopoverContent.dark{ - background-color: #1F2936; + background-color: #1F2936 !important; .dark.leftsidebar-panel-header{ .text-muted{ color: #C0C8CC !important; diff --git a/frontend/src/_styles/queryManager.scss b/frontend/src/_styles/queryManager.scss index 9819da51be..2e8c68d8d9 100644 --- a/frontend/src/_styles/queryManager.scss +++ b/frontend/src/_styles/queryManager.scss @@ -1,5 +1,6 @@ @import "./colors.scss"; $border-radius: 4px; + .query-manager { user-select: none; @@ -35,6 +36,7 @@ $border-radius: 4px; font-weight: 600; } } + .query-pane { scrollbar-width: none; } @@ -91,7 +93,8 @@ $border-radius: 4px; .query-copy-button { display: none; } - .query-rename-delete-btn{ + + .query-rename-delete-btn { display: none; align-items: center; gap: 8px; @@ -100,6 +103,7 @@ $border-radius: 4px; width: 44px; height: 20px; } + .query-name { white-space: nowrap; overflow: hidden; @@ -108,67 +112,77 @@ $border-radius: 4px; display: flex; font-weight: 400; } - .query-name-input-field{ + + .query-name-input-field { flex: 1; width: 100%; height: 20px !important; } - .query-icon{ + + .query-icon { margin: auto 8px auto 12px; width: 21.33px; height: 21.33px; padding: 1.33px; - svg{ + + svg { width: 20px !important; height: 20px !important; } } - .delete-query,.rename-query{ - // display: none; - span{ + + .delete-query, + .rename-query { + + span { width: 16px; height: 16px; padding: 1.33px 2px; } } + &:hover { - background: $color-light-slate-03 !important; - + background: $color-light-slate-03 !important; + } - .query-row-query-name{ - padding-top: '4px'; + + .query-row-query-name { + padding-top: '4px'; padding-bottom: '4px'; transition-delay: '10ms'; } } - .query-row.border{ - border-color: $color-light-indigo-10 !important; + + .query-row.border { + border-color: $color-light-indigo-10 !important; } + .query-row-selected { - background:$color-light-indigo-04 !important; - // .delete-query,.rename-query{ - // display: block; - // } - .delete-query{ - svg:hover{ - path{ + background: $color-light-indigo-04 !important; + + .delete-query { + svg:hover { + path { fill: $color-light-tomato-09; } } } - .rename-query{ - svg:hover{ - path{ + + .rename-query { + svg:hover { + path { fill: $color-light-slate-11; } } } + &:hover { - background: $color-light-slate-03 !important; + background: $color-light-slate-03 !important; } - .rename-query.display-none{ + + .rename-query.display-none { visibility: hidden; } } @@ -215,16 +229,19 @@ $border-radius: 4px; max-height: 44px; height: 44px; font-weight: 500 !important; - .query-manager-header-query-name{ + + .query-manager-header-query-name { color: $color-light-slate-12; } - .ellipsis{ - width: 100px; + + .ellipsis { + width: 100px; white-space: nowrap; - overflow: hidden; + overflow: hidden; text-overflow: ellipsis; } - .query-header-buttons{ + + .query-header-buttons { gap: 8px; } } @@ -243,6 +260,7 @@ $border-radius: 4px; -ms-overflow-style: none; /* Internet Explorer 10+ */ user-select: none; + &::-webkit-scrollbar { /* WebKit */ width: 0; @@ -266,34 +284,40 @@ $border-radius: 4px; justify-content: center; padding: 8px 12px; gap: 12px; - .queries-search{ + + .queries-search { width: 172px !important; height: 28px !important; - .query-manager-search-box-wrapper{ - - .input-icon{ - + + .query-manager-search-box-wrapper { + + .input-icon { + margin-bottom: 0 !important; - .input-icon-addon{ + + .input-icon-addon { margin: 6px 0 6px 8px; padding: 1.33px 0 1.33px 1.33px; justify-content: normal; - width: 16px ; + width: 16px; height: 16px; min-width: 16px; align-items: center; - .icon{ + + .icon { width: 13.33px; height: 13.33px; stroke: $color-light-slate-08; } } - .form-control{ + + .form-control { padding-top: 4px !important; padding-bottom: 4px !important; - padding-right: 8px ; + padding-right: 8px; } - input{ + + input { height: 28px; font-size: 12px; font-weight: 400; @@ -301,31 +325,36 @@ $border-radius: 4px; border: 0; color: $color-light-slate-12; padding-left: 12px !important; - &::placeholder{ + + &::placeholder { color: $color-light-slate-09; } - &:focus{ + + &:focus { height: 28px; - border:1px solid $color-light-indigo-09 ; + border: 1px solid $color-light-indigo-09 ; background-color: $color-light-indigo-02; box-shadow: 0px 0px 0px 2px #C6D4F9 !important; overflow: hidden; text-overflow: clip; } - + } - .form-control:not(:first-child){ + + .form-control:not(:first-child) { padding-left: 2rem !important; - + } } - .input-icon-addon.end{ + + .input-icon-addon.end { display: flex; width: 20px; height: 20px; padding: 2px; margin: 4px 10px 4px 8px !important; - div{ + + div { background-color: #f0f4ff; width: 16px; height: 16px; @@ -333,35 +362,40 @@ $border-radius: 4px; display: flex; border-radius: 2px; } + svg { width: auto !important; height: auto !important; - stroke : $color-light-indigo-09 !important; - + stroke: $color-light-indigo-09 !important; + } } } - + } - .queries-search.theme-dark{ - .query-manager-search-box-wrapper{ - .input-icon-addon:first-child{ - .icon{ + + .queries-search.theme-dark { + .query-manager-search-box-wrapper { + .input-icon-addon:first-child { + .icon { stroke: #ffffff !important; } } - input{ - color : inherit; - &::placeholder{ + + input { + color: inherit; + + &::placeholder { color: inherit; } - &:focus{ + + &:focus { height: 28px; - border:1px solid $color-light-indigo-09 ; + border: 1px solid $color-light-indigo-09 ; background-color: inherit; box-shadow: 0px 0px 0px 2px #C6D4F9 !important; } - + } } } @@ -371,7 +405,8 @@ $border-radius: 4px; width: 0; background: transparent; } - .query-list{ + + .query-list { display: flex; flex-direction: column; justify-content: center; @@ -379,7 +414,8 @@ $border-radius: 4px; padding: 8px; gap: 2px; width: 100%; - .mute-text{ + + .mute-text { color: #6B7787; } } @@ -414,9 +450,11 @@ $border-radius: 4px; height: fit-content; width: 100%; margin-top: 20px; - .codehinter-default-input{ + + .codehinter-default-input { border: 0 !important; } + .row { height: inherit; @@ -448,24 +486,27 @@ $border-radius: 4px; .tab-content-wrapper { display: flex; flex-direction: column; - .query-number{ + + .query-number { max-height: 32px; flex: 0 0 32px; background-color: #F8F9FA; color: #000; } - .delete-field-option{ - max-height: 32px; - flex: 0 0 28px; + + .delete-field-option { + max-height: 32px; + flex: 0 0 28px; background: #ffffff; max-width: 28px !important; width: 28px; } - .code-hinter.codehinter-default-input{ + + .code-hinter.codehinter-default-input { border: 1px solid transparent !important; } - .code-hinter.codehinter-default-input:focus-within{ - // box-shadow: 0px 0px 0px 1px #C6D4F9 !important; + + .code-hinter.codehinter-default-input:focus-within { border: 1px solid #3E63DD !important; background-color: #F8FAFF; border-radius: 0; @@ -485,20 +526,23 @@ $border-radius: 4px; justify-content: space-between; width: 100%; } - .fields-container{ - .field:nth-child(3){ + + .fields-container { + .field:nth-child(3) { border-left: 1px solid $color-light-slate-07; border-right: 1px solid $color-light-slate-07; } } - .CodeMirror{ + + .CodeMirror { border-radius: 0 !important; font-size: 12px; } } + .content-title { - p{ - margin-right: 10px; + p { + margin-right: 10px; color: $color-dark-slate-12; } @@ -514,15 +558,18 @@ $border-radius: 4px; color: #ffffff !important; } } - .tab-content-wrapper{ - .query-number{ + + .tab-content-wrapper { + .query-number { background-color: inherit !important; color: #fff !important; } - .delete-field-option{ + + .delete-field-option { background: transparent !important; } - .code-hinter.codehinter-default-input:focus-within{ + + .code-hinter.codehinter-default-input:focus-within { background-color: inherit !important; } } @@ -530,26 +577,31 @@ $border-radius: 4px; .list-group-item.active { color: $white !important; } - .fields-container{ - .field{ + + .fields-container { + .field { border: 0 !important; outline: none !important; } - .field:nth-child(3){ - border-left: 1px solid $color-dark-slate-07 !important; - border-right: 1px solid $color-dark-slate-07 !important; + + .field:nth-child(3) { + border-left: 1px solid $color-dark-slate-07 !important; + border-right: 1px solid $color-dark-slate-07 !important; } } - .cm-s-monokai.CodeMirror{ + + .cm-s-monokai.CodeMirror { background-color: inherit; } - .CodeMirror{ + + .CodeMirror { border-radius: 0 !important; font-size: 12px; border: 0 !important; } + .content-title { - p{ + p { color: #a3a3a3 !important; } @@ -572,14 +624,16 @@ $border-radius: 4px; color: $color-light-slate-11; display: flex; align-items: center; + span { padding: 6px 8px; } } + .list-group-item:hover { - color: $color-light-slate-12 !important; - background-color: $color-light-slate-03 !important; + color: $color-light-slate-12 !important; + background-color: $color-light-slate-03 !important; border-radius: 6px; } @@ -591,33 +645,37 @@ $border-radius: 4px; background-color: transparent !important; color: $color-light-indigo-09; z-index: inherit !important; - border-bottom: 2px solid $color-light-indigo-09 !important; + border-bottom: 2px solid $color-light-indigo-09 !important; } - .list-group-item.active:hover{ - background-color: $color-light-indigo-03 !important; + + .list-group-item.active:hover { + background-color: $color-light-indigo-03 !important; border-radius: 6px 6px 0 0; - color: $color-light-indigo-09 !important; - } + color: $color-light-indigo-09 !important; + } } -.query-pane-restapi-tabs.dark{ - .query-pane-rest-api-keys-list-group{ + +.query-pane-restapi-tabs.dark { + .query-pane-rest-api-keys-list-group { .list-group-item { color: #9E9EA8 !important; } + .list-group-item:hover { - color: $color-dark-slate-12 !important; - background-color: $color-dark-slate-03 !important; + color: $color-dark-slate-12 !important; + background-color: $color-dark-slate-03 !important; } - + .list-group-item.active { - color: $color-dark-indigo-09 !important; + color: $color-dark-indigo-09 !important; border-bottom: 2px solid $color-dark-indigo-09 !important; } - .list-group-item.active:hover{ - background-color: $color-dark-indigo-03 !important; - color: $color-dark-indigo-09 !important; + + .list-group-item.active:hover { + background-color: $color-dark-indigo-03 !important; + color: $color-dark-indigo-09 !important; } - + } } @@ -633,12 +691,13 @@ $border-radius: 4px; cursor: pointer; font-weight: 500; font-size: 12px; - padding:0 !important; + padding: 0 !important; height: 28px; color: $color-light-slate-11; border-radius: 6px; display: flex; align-items: center; + span { padding: 6px 8px; } @@ -647,9 +706,10 @@ $border-radius: 4px; color: #000; } } + .list-group-item:hover { - color: $color-light-slate-12 !important; - background-color: $color-light-slate-03 !important; + color: $color-light-slate-12 !important; + background-color: $color-light-slate-03 !important; border-radius: 6px; } @@ -657,19 +717,20 @@ $border-radius: 4px; margin-top: 0; } - + .list-group-item.active { background-color: transparent !important; - color: $color-light-indigo-09 !important; + color: $color-light-indigo-09 !important; z-index: inherit !important; border-bottom: 2px solid $color-light-indigo-09 !important; border-radius: 0; transition-delay: 5ms; } - .list-group-item.active:hover{ - background-color: $color-light-indigo-03 !important; + + .list-group-item.active:hover { + background-color: $color-light-indigo-03 !important; border-radius: 6px 6px 0 0; - color: $color-light-indigo-09 !important; + color: $color-light-indigo-09 !important; } } @@ -677,25 +738,28 @@ $border-radius: 4px; list-group-item { color: $color-dark-slate-11 !important; } + .list-group-item:hover { - color: $color-dark-slate-12 !important; - background-color: $color-dark-slate-03 !important; + color: $color-dark-slate-12 !important; + background-color: $color-dark-slate-03 !important; } .list-group-item.active { - color: $color-dark-indigo-09 !important; + color: $color-dark-indigo-09 !important; border-bottom: 2px solid $color-dark-indigo-09 !important; } - .list-group-item.active:hover{ - background-color: $color-dark-indigo-03 !important; - color: $color-dark-indigo-09 !important; + + .list-group-item.active:hover { + background-color: $color-dark-indigo-03 !important; + color: $color-dark-indigo-09 !important; } } + /** * *Stripe Query Select-search and OpenApi */ - .stripe-operation-options .select-search__row .col-md-8 { +.stripe-operation-options .select-search__row .col-md-8 { margin-left: 45px !important; } @@ -706,35 +770,47 @@ $border-radius: 4px; border-radius: $border-radius !important; } } -.stripe-fields-row,.openApi-fields-row{ - .path-fields:first-child,.request-body-fields:first-child,.query-fields:first-child{ + +.stripe-fields-row, +.openApi-fields-row { + + .path-fields:first-child, + .request-body-fields:first-child, + .query-fields:first-child { margin-top: 12px !important; } - .field-width-179{ + + .field-width-179 { width: 179px; height: 32px; } - .field-width-28{ + + .field-width-28 { width: 28px; height: 32px; padding: 12.5px 10.5px; } - .input-group-parent-container{ - border: 1px solid $color-light-slate-07 !important; + + .input-group-parent-container { + border: 1px solid $color-light-slate-07 !important; border-radius: 6px !important; overflow: hidden; - &>.input-group-wrapper{ + + &>.input-group-wrapper { border-bottom: 1px solid $color-light-slate-07; } - &>.input-group-wrapper:last-child{ + + &>.input-group-wrapper:last-child { border-bottom: 0 !important; } } - .input-group-wrapper{ + + .input-group-wrapper { overflow: hidden; - .input-group{ - .field{ - .form-control{ + + .input-group { + .field { + .form-control { height: 32px; border: 0 !important; border-radius: 0 !important; @@ -743,139 +819,172 @@ $border-radius: 4px; font-size: 12px; line-height: 20px; } - .code-hinter-col{ + + .code-hinter-col { margin-bottom: 0 !important; - .code-hinter-wrapper{ + + .code-hinter-wrapper { border-width: 0 1px 0 1px; border-style: solid; border-color: $color-light-slate-07; } - .code-hinter.codehinter-default-input{ + + .code-hinter.codehinter-default-input { border: none; - padding: 0 ; + padding: 0; border-radius: 0 !important; overflow: auto; - .CodeMirror.cm-s-duotone-light.CodeMirror-wrap{ + + .CodeMirror.cm-s-duotone-light.CodeMirror-wrap { border: 1px solid transparent !important; margin: 0 1px; overflow: hidden !important; } - .CodeMirror.cm-s-duotone-light.CodeMirror-wrap:focus-within{ - width: 99.8% !important; - background-color:#F8FAFF !important; - border : 1px solid #3E63DD !important; + + .CodeMirror.cm-s-duotone-light.CodeMirror-wrap:focus-within { + width: 99.8% !important; + background-color: #F8FAFF !important; + border: 1px solid #3E63DD !important; margin: 0 1px; border-radius: 0; } - .CodeMirror-line{ + + .CodeMirror-line { color: #000; font-size: 12px; line-height: 20px; font-weight: 400; } } - .cm-s-duotone-light.CodeMirror{ + + .cm-s-duotone-light.CodeMirror { background-color: inherit !important; } } } } } - .text-heading{ + + .text-heading { color: $color-light-slate-12; font-weight: 400; line-height: 20px; } - .request-body-fields,.path-fields,.query-fields{ + + .request-body-fields, + .path-fields, + .query-fields { margin-top: 28px; } - .stripe-operation-options{ - p{ + + .stripe-operation-options { + p { margin-bottom: 0 !important; margin-top: 12px !important; display: inline-block !important; } } } -.openApi-fields-row{ - .path-fields:first-child,.request-body-fields:first-child,.query-fields:first-child{ + +.openApi-fields-row { + + .path-fields:first-child, + .request-body-fields:first-child, + .query-fields:first-child { margin-top: 28px !important; } } -.stripe-fields-row.theme-dark,.openApi-fields-row.theme-dark{ - .form-control{ + +.stripe-fields-row.theme-dark, +.openApi-fields-row.theme-dark { + .form-control { background-color: inherit !important; color: inherit !important; } - .field-width-28{ - svg{ - path{ + + .field-width-28 { + svg { + path { fill: #fff; } } } - .text-heading{ + + .text-heading { color: inherit; } - .code-hinter-wrapper{ + + .code-hinter-wrapper { border-color: #ffffff17 !important; } - .input-group-parent-container{ - border: 1px solid #ffffff17 !important; - &>.input-group-wrapper{ + + .input-group-parent-container { + border: 1px solid #ffffff17 !important; + + &>.input-group-wrapper { border-bottom: 1px solid #ffffff17; } - &>.input-group-wrapper:last-child{ + + &>.input-group-wrapper:last-child { border-bottom: 0 !important; } } - .CodeMirror-line{ + + .CodeMirror-line { color: inherit !important; } - .CodeMirror.cm-s-monokai.CodeMirror-wrap{ + + .CodeMirror.cm-s-monokai.CodeMirror-wrap { border: 1px solid transparent; margin: 0 1px; } - .CodeMirror.cm-s-monokai.CodeMirror-wrap:focus-within{ + + .CodeMirror.cm-s-monokai.CodeMirror-wrap:focus-within { width: 99.8% !important; - border : 1px solid #3E63DD !important; - margin: 0 1px ; + border: 1px solid #3E63DD !important; + margin: 0 1px; border-radius: 0; } } -.data-pane{ - .queries-container.theme-dark{ - .query-row{ +.data-pane { + .queries-container.theme-dark { + .query-row { color: #f4f6fa; } - .query-row-selected{ - background: #2b3546 !important; + + .query-row-selected { + background: #2b3546 !important; } + .query-row:hover { background: #404d66 !important; } - .delete-query{ - svg{ - path{ - fill: $color-dark-tomato-10 !important; + + .delete-query { + svg { + path { + fill: $color-dark-tomato-10 !important; } } - svg:hover{ - path{ - fill: $color-dark-tomato-09 !important; + + svg:hover { + path { + fill: $color-dark-tomato-09 !important; } } } - .rename-query{ - svg{ - path{ - fill: $color-dark-slate-12 !important; + + .rename-query { + svg { + path { + fill: $color-dark-slate-12 !important; } } - svg:hover{ - path{ - fill: $color-dark-slate-11 !important; + + svg:hover { + path { + fill: $color-dark-slate-11 !important; } } } @@ -885,111 +994,129 @@ $border-radius: 4px; } //query manager -.query-manager{ - .query-name-breadcrum{ +.query-manager { + .query-name-breadcrum { cursor: pointer; - .breadcrum-rename-query-icon{ + + .breadcrum-rename-query-icon { display: none; } - &:hover{ - .breadcrum-rename-query-icon{ + + &:hover { + .breadcrum-rename-query-icon { display: inline-flex; height: 14px; width: 14px; margin-left: 8px; padding: 1.75px; - &:hover{ - svg{ - path{ - fill: $color-light-slate-11 !important; + + &:hover { + svg { + path { + fill: $color-light-slate-11 !important; } } } } } } - .code-hinter.codehinter-default-input{ - &:hover{ - background-color: #FBFCFD ; + + .code-hinter.codehinter-default-input { + &:hover { + background-color: #FBFCFD; border-radius: 6px; } } - .breadcrum{ - width: 15.33px ; - height: 15.33px ; + + .breadcrum { + width: 15.33px; + height: 15.33px; padding: 3px 5px; display: flex; } + input[type=checkbox]:checked { background-color: $color-light-indigo-09; - + } + input[type=checkbox]:checked:active { background-color: $color-light-indigo-09; box-shadow: 0px 0px 0px 4px #C6D4F9; border-radius: 12px; } + input[type=checkbox] { background-color: $color-light-slate-07; cursor: pointer !important; border: 0; } + input[type=checkbox]:active { background-color: $color-light-indigo-04; box-shadow: 0px 0px 0px 4px #C6D4F9; border-radius: 12px; } + input[type=checkbox]:hover { background-color: $color-light-indigo-05; - + } + input[type=checkbox]:checked:hover { background-color: $color-light-indigo-11; - + } - .CodeMirror-placeholder{ + + .CodeMirror-placeholder { margin-top: 0 !important; } - .preview-section{ - ul{ + + .preview-section { + ul { margin: 0 !important; padding: 0.5em !important; } - .preview-default-container{ + + .preview-default-container { user-select: text; background-color: #F8F9FA; border: 0 0 6px 6px; height: 52px, } - .tab-pane.active{ - div{ + + .tab-pane.active { + div { background-color: #F8F9FA !important; border-radius: 0 0 6px 6px !important; } - ul{ - background-color: transparent !important ; + + ul { + background-color: transparent !important; } } } - .query-manager-border-color{ - border-color: $color-light-slate-05 !important; + + .query-manager-border-color { + border-color: $color-light-slate-05 !important; } - .query-details{ - .form-label{ - color: $color-light-slate-12 !important; + .query-details { + .form-label { + color: $color-light-slate-12 !important; font-size: 12px; font-weight: 400; line-height: 20px; } } - - .rest-methods-url{ - .code-hinter.codehinter-default-input{ + + .rest-methods-url { + .code-hinter.codehinter-default-input { border-style: solid !important; - border-color: $color-light-slate-07 !important; + border-color: $color-light-slate-07 !important; border-radius: 0 6px 6px 0 !important; - &:focus-within{ + + &:focus-within { box-shadow: 0px 0px 0px 2px #C6D4F9 !important; border: 1px solid #3E63DD !important; background-color: #F8FAFF; @@ -997,346 +1124,431 @@ $border-radius: 4px; z-index: 1 !important; } } - .CodeMirror.CodeMirror-wrap{ + + .CodeMirror.CodeMirror-wrap { background-color: transparent !important; - .cm-variable,.cm-comment{ + + .cm-variable, + .cm-comment { font-size: 12px !important; - color: $color-light-slate-12 !important; + color: $color-light-slate-12 !important; } } } - .rest-api-methods-select-element-container{ + + .rest-api-methods-select-element-container { display: flex; flex-direction: row; - .col.code-hinter-col{ + + .col.code-hinter-col { margin-bottom: 0 !important; - + } } - .advanced-options-container{ - .advance-options-input-form-container{ - margin-top: 24px ; - margin-bottom: 24px ; + .advanced-options-container { + .advance-options-input-form-container { + margin-top: 24px; + margin-bottom: 24px; } - .form-check{ + + .form-check { margin-bottom: 0 !important; } - .CodeMirror-code{ + + .CodeMirror-code { color: $color-light-grass-11 ; } - .form-label{ - color: $color-light-slate-11 !important; - } - .form-check-label{ + + .form-label { + color: $color-light-slate-11 !important; + } + + .form-check-label { color: $color-light-slate-12; } - .cm-s-monokai.CodeMirror,.query-manager-input-elem>input{ - color: $color-light-slate-12 !important; + + .cm-s-monokai.CodeMirror, + .query-manager-input-elem>input { + color: $color-light-slate-12 !important; } - .codehinter-default-input:focus-within,.query-manager-input-elem>input:focus{ + + .codehinter-default-input:focus-within, + .query-manager-input-elem>input:focus { box-shadow: 0px 0px 0px 2px #C6D4F9 !important; border: 1px solid #3E63DD !important; background-color: #F8FAFF; } - .code-hinter.codehinter-default-input,.query-manager-input-elem > input{ + + .code-hinter.codehinter-default-input, + .query-manager-input-elem>input { height: 32px !important; font-weight: 400; } - .query-manager-events{ - .card{ - .event-handler-display,.event-name-display{ - color:$color-light-slate-12 !important; + + .query-manager-events { + .card { + + .event-handler-display, + .event-name-display { + color: $color-light-slate-12 !important; } - .text-danger{ - svg{ - path{ - fill:$color-light-tomato-09 !important; + + .text-danger { + svg { + path { + fill: $color-light-tomato-09 !important; } } - &:hover{ - svg{ - path{ - fill:$color-light-tomato-10 !important; + + &:hover { + svg { + path { + fill: $color-light-tomato-10 !important; } } } } - + } - .inspector-add-button{ + + .inspector-add-button { background-color: transparent; font-weight: 400 !important; font-size: 12px !important; - color: $color-light-indigo-09 !important; - &:hover{ + color: $color-light-indigo-09 !important; + + &:hover { background-color: #E6EDFE !important; color: #3451B2 !important; } } } } - .transformation-editor{ + + .transformation-editor { margin-top: 28px; - & > div.rounded-3{ + + &>div.rounded-3 { padding: 0 24px !important; } - .tranformation-label{ + + .tranformation-label { color: $color-light-slate-12; } - .CodeMirror-scroll,.CodeMirror-gutters{ + + .CodeMirror-scroll, + .CodeMirror-gutters { background-color: $color-light-slate-02 ; } - .transformation-language-select-wrapper{ + + .transformation-language-select-wrapper { background: $color-light-slate-04; - border: 1px solid $color-light-slate-07 !important; + border: 1px solid $color-light-slate-07 !important; border-radius: 0; border-radius: 6px 0 0 6px; - span{ + + span { color: $color-light-slate-11; } } } - .runjs-editor{ - .CodeMirror-scroll,.CodeMirror-gutters{ + + .runjs-editor { + + .CodeMirror-scroll, + .CodeMirror-gutters { background-color: $color-light-slate-02; } - .query-hinter{ + + .query-hinter { textarea:first-of-type { opacity: 0.1; // Added to avoid displaying textarea while switching from one RunJS Query to another } } } - .query-editor-dynamic-form-container{ + + .query-editor-dynamic-form-container { font-weight: 400; - .css-1goth4y-control:hover{ + + .css-1goth4y-control:hover { background-color: #FBFCFD; border-radius: 6px; } - .css-1goth4y-control:active{ - box-shadow: 0px 0px 0px 2px #C6D4F9 !important; - border: 1px solid #3E63DD !important; - border-radius: 6px; - background-color: #F8FAFF; - } - .css-1goth4y-control{ - cursor: pointer !important; - } - - .query-hinter{ - .CodeMirror-scroll,.CodeMirror-gutters{ - background-color: $color-light-slate-02 ; - - } - } - .codehinter-plugins{ - .code-hinter.codehinter-plugins{ - height: 32px !important; - padding: 2px 0 !important; - font-size: 12px !important; - .CodeMirror-lines{ - padding: 0 !important; - } - } - } - .CodeMirror-line{ - font-size: 12px; - } - .code-hinter.codehinter-plugins:focus-within{ + + .css-1goth4y-control:active { box-shadow: 0px 0px 0px 2px #C6D4F9 !important; border: 1px solid #3E63DD !important; border-radius: 6px; background-color: #F8FAFF; } - & > .row:first-child{ - .my-2{ + + .css-1goth4y-control { + cursor: pointer !important; + } + + .query-hinter { + + .CodeMirror-scroll, + .CodeMirror-gutters { + background-color: $color-light-slate-02 ; + + } + } + + .codehinter-plugins { + .code-hinter.codehinter-plugins { + height: 32px !important; + padding: 2px 0 !important; + font-size: 12px !important; + + .CodeMirror-lines { + padding: 0 !important; + } + } + } + + .CodeMirror-line { + font-size: 12px; + } + + .code-hinter.codehinter-plugins:focus-within { + box-shadow: 0px 0px 0px 2px #C6D4F9 !important; + border: 1px solid #3E63DD !important; + border-radius: 6px; + background-color: #F8FAFF; + } + + &>.row:first-child { + .my-2 { margin: 0 !important; } } - & > .row:not(:first-child){ - .my-2{ - margin: 20px 0 0 0 !important; + + &>.row:not(:first-child) { + .my-2 { + margin: 20px 0 0 0 !important; } } - .code-hinter-wrapper{ + + .code-hinter-wrapper { border-radius: 6px; } } - .delete-field-option:hover{ - svg{ - path{ - fill: $color-light-tomato-10 !important; + + .delete-field-option:hover { + svg { + path { + fill: $color-light-tomato-10 !important; } } } - .add-tabs:hover{ - svg{ - path{ - fill: $color-light-indigo-10 !important; + + .add-tabs:hover { + svg { + path { + fill: $color-light-indigo-10 !important; } } } - .query-datasource-card-container{ - .query-datasource-card{ - color: $color-light-slate-12 !important; + + .query-datasource-card-container { + .query-datasource-card { + color: $color-light-slate-12 !important; font-weight: 400; font-size: 12px; height: 32px; - svg{ + + svg { width: 16px !important; height: 16px !important; } } } - + } -.query-manager.theme-dark{ - .breadcrum-rename-query-icon{ - svg{ - path{ +.query-manager.theme-dark { + .breadcrum-rename-query-icon { + svg { + path { fill: $color-dark-slate-12; } } - &:hover{ - svg{ - path{ + + &:hover { + svg { + path { fill: $color-dark-slate-11; } } } } - .preview-section{ - .preview-default-container{ + + .preview-section { + .preview-default-container { background-color: #272822; } - .tab-pane{ - div{ + + .tab-pane { + div { background-color: #272822 !important; } } } - .query-manager-border-color{ + + .query-manager-border-color { border-color: #ffffff17 !important; } - .code-hinter.codehinter-default-input,.query-select-dropdown~.css-1tkif1k-container:focus{ - &:hover{ + + .code-hinter.codehinter-default-input, + .query-select-dropdown~.css-1tkif1k-container:focus { + &:hover { background-color: #282926 !important; } } - .query-details{ - .form-label{ + + .query-details { + .form-label { color: #f4f6fa !important; font-size: 12px; font-weight: 400; line-height: 20px; } } - .row.header{ - .breadcrum{ - svg{ - path{ - fill: #4C5155 ; + + .row.header { + .breadcrum { + svg { + path { + fill: #4C5155; } } } - .query-manager-header-query-name{ + + .query-manager-header-query-name { color: #f4f6fa !important; } } - .transformation-editor{ - .css-5g0ati-control{ - border: 0 ; + + .transformation-editor { + .css-5g0ati-control { + border: 0; border-radius: 0 4px 4px 0; background: #2B3546 !important; - &:focus-within{ - border: 1px solid $color-light-indigo-09 !important; + + &:focus-within { + border: 1px solid $color-light-indigo-09 !important; } } - .tranformation-label{ + + .tranformation-label { color: #fff; } - .css-ppbcid-singleValue{ - color: $color-dark-slate-12 !important; + + .css-ppbcid-singleValue { + color: $color-dark-slate-12 !important; } - .CodeMirror-scroll,.CodeMirror-gutters{ + + .CodeMirror-scroll, + .CodeMirror-gutters { background-color: inherit; } - .transformation-language-select-wrapper{ + + .transformation-language-select-wrapper { background: $color-dark-slate-04; - border-style: solid; + border-style: solid; border-color: #cccccc; border-width: 1px 0 1px 1px !important; - span{ + + span { color: $color-dark-slate-11; } } } - .advanced-options-container{ - .CodeMirror-code{ - color: $color-dark-grass-11 !important; + + .advanced-options-container { + .CodeMirror-code { + color: $color-dark-grass-11 !important; } - .form-label{ + + .form-label { color: #f4f6fa !important; - } - .form-check-label{ + } + + .form-check-label { color: $color-dark-slate-12; } - .cm-s-monokai.CodeMirror,.query-manager-input-elem>input{ - color: $color-dark-slate-12 !important; + + .cm-s-monokai.CodeMirror, + .query-manager-input-elem>input { + color: $color-dark-slate-12 !important; } - .codehinter-default-input:focus-within,.query-manager-input-elem>input:focus{ + + .codehinter-default-input:focus-within, + .query-manager-input-elem>input:focus { box-shadow: 0px 0px 0px 2px #C6D4F9 !important; border: 1px solid #3E63DD !important; background-color: inherit; } - .query-manager-events{ - .card{ - .event-handler-display,.event-name-display{ - color:inherit !important; + + .query-manager-events { + .card { + + .event-handler-display, + .event-name-display { + color: inherit !important; } } - .text-danger{ - svg{ - path{ - fill:$color-dark-tomato-09 !important; + + .text-danger { + svg { + path { + fill: $color-dark-tomato-09 !important; } } - &:hover{ - svg{ - path{ - fill:$color-dark-tomato-10 !important; + + &:hover { + svg { + path { + fill: $color-dark-tomato-10 !important; } } } } - .inspector-add-button{ + + .inspector-add-button { background-color: inherit; color: $color-dark-indigo-09; - &:hover{ + + &:hover { background-color: inherit; color: inherit; } } } } - .rest-api-methods-select-element-container{ - .col.code-hinter-col{ + + .rest-api-methods-select-element-container { + .col.code-hinter-col { margin-bottom: 0 !important; } - .css-17xqmvg-container{ - .css-5g0ati-control{ + + .css-17xqmvg-container { + .css-5g0ati-control { cursor: pointer; border-radius: 6px 0 0 6px !important; - svg{ - path{ - fill: $color-dark-slate-11 !important; + + svg { + path { + fill: $color-dark-slate-11 !important; } } - &:focus-within{ + + &:focus-within { border: 1px solid $color-dark-indigo-09; background-color: inherit; box-shadow: 0px 0px 0px 2px #C6D4F9; @@ -1344,19 +1556,23 @@ $border-radius: 4px; } } } - .rest-methods-url{ - .code-hinter.codehinter-default-input{ + + .rest-methods-url { + .code-hinter.codehinter-default-input { border: solid inherit !important; border-radius: 0 6px 6px 0 !important; - .CodeMirror.cm-s-monokai.CodeMirror-wrap{ + + .CodeMirror.cm-s-monokai.CodeMirror-wrap { background-color: transparent !important; - .cm-variable,.cm-comment{ + + .cm-variable, + .cm-comment { font-size: 12px !important; color: #f8f8f2 !important; } } - - &:focus-within{ + + &:focus-within { box-shadow: 0px 0px 0px 2px #C6D4F9 !important; border: 1px solid #3E63DD !important; background-color: inherit !important; @@ -1364,60 +1580,76 @@ $border-radius: 4px; } } - .runjs-editor{ - .CodeMirror-scroll,.CodeMirror-gutters{ + + .runjs-editor { + + .CodeMirror-scroll, + .CodeMirror-gutters { background-color: inherit; } } - .query-editor-dynamic-form-container{ - .query-hinter{ - .CodeMirror-scroll,.CodeMirror-gutters{ - background-color: transparent !important ; + + .query-editor-dynamic-form-container { + .query-hinter { + + .CodeMirror-scroll, + .CodeMirror-gutters { + background-color: transparent !important; } } - .css-5g0ati-control{ + + .css-5g0ati-control { cursor: pointer !important; } - .css-5g0ati-control:active{ + + .css-5g0ati-control:active { box-shadow: 0px 0px 0px 2px #C6D4F9 !important; border: 1px solid #3E63DD !important; border-radius: 6px; } - .code-hinter.codehinter-plugins:focus-within{ - background-color: inherit ; + + .code-hinter.codehinter-plugins:focus-within { + background-color: inherit; } } - .delete-field-option:hover{ - svg{ - path{ - fill: $color-dark-tomato-10 !important; + + .delete-field-option:hover { + svg { + path { + fill: $color-dark-tomato-10 !important; } } } - .add-tabs:hover{ - svg{ - path{ - fill: $color-dark-indigo-10 !important; + + .add-tabs:hover { + svg { + path { + fill: $color-dark-indigo-10 !important; } } } - .query-datasource-card-container{ - .query-datasource-card{ + + .query-datasource-card-container { + .query-datasource-card { color: #fff !important; } } } -.query-icon-wrapper{ + +.query-icon-wrapper { width: 16px; height: 16px; } -.query-manager-btn-svg-wrapper{ + +.query-manager-btn-svg-wrapper { padding: 2.67px; } -.rest-api-delete-field-option{ + +.rest-api-delete-field-option { padding: 1.33px 2px; } -.rest-api-add-field-svg{ + +.rest-api-add-field-svg { width: 18px; height: 18px; padding: 3px; @@ -1425,21 +1657,22 @@ $border-radius: 4px; } // custom-toggle-switch for query manager -.custom-toggle-switch{ +.custom-toggle-switch { display: flex; + .switch { position: relative; display: inline-block; width: 28px; height: 16px; } - - .switch input { + + .switch input { opacity: 0; width: 0; height: 0; } - + .slider { position: absolute; cursor: pointer; @@ -1451,11 +1684,11 @@ $border-radius: 4px; -webkit-transition: .4s; transition: .4s; } - + .slider:before { position: absolute; content: ""; - height:12px; + height: 12px; width: 12px; left: 2px; bottom: 2px; @@ -1463,71 +1696,80 @@ $border-radius: 4px; -webkit-transition: .2s; transition: .2s; } - - input{ + + input { border: 0 !important; } - input[type=checkbox]:checked + .slider { - background-color: #3E63DD ; + + input[type=checkbox]:checked+.slider { + background-color: #3E63DD; } - input[type=checkbox]:checked:hover + .slider{ - background-color: #3451B2 ; + + input[type=checkbox]:checked:hover+.slider { + background-color: #3451B2; } - input[type=checkbox]:checked:active + .slider { - background-color: #3E63DD ; - box-shadow: 0px 0px 0px 4px #C6D4F9 ; + + input[type=checkbox]:checked:active+.slider { + background-color: #3E63DD; + box-shadow: 0px 0px 0px 4px #C6D4F9; } - - input[type=checkbox]:checked + .slider:before { + + input[type=checkbox]:checked+.slider:before { -webkit-transform: translateX(100%); -ms-transform: translateX(100%); transform: translateX(100%); } - input[type=checkbox] + .slider{ - background-color: #D7DBDF; - } - input[type=checkbox]:hover + .slider { + input[type=checkbox]+.slider { + background-color: var(--slate7); + } + + input[type=checkbox]:hover+.slider { background-color: #D9E2FC; } - - input[type=checkbox]:active + .slider { - background: #E6EDFE ; - box-shadow: 0px 0px 0px 4px #C6D4F9 ; + + input[type=checkbox]:active+.slider { + background: #E6EDFE; + box-shadow: 0px 0px 0px 4px #C6D4F9; border-radius: 12px; } - + /* Rounded sliders */ .slider.round { border-radius: 12px; } - + .slider.round:before { border-radius: 50%; } } -.custom-toggle-switch.theme-dark{ - input[type=checkbox] + .slider{ + +.custom-toggle-switch.theme-dark { + input[type=checkbox]+.slider { background-color: #47505D !important; } - input[type=checkbox]:hover + .slider { + + input[type=checkbox]:hover+.slider { background-color: #D9E2FC; } - - input[type=checkbox]:active + .slider { - background: #E6EDFE ; - box-shadow: 0px 0px 0px 4px #C6D4F9 ; + + input[type=checkbox]:active+.slider { + background: #E6EDFE; + box-shadow: 0px 0px 0px 4px #C6D4F9; border-radius: 12px; } - input[type=checkbox]:checked + .slider { + + input[type=checkbox]:checked+.slider { background-color: #3E63DD !important; } - input[type=checkbox]:checked:hover + .slider{ + + input[type=checkbox]:checked:hover+.slider { background-color: #3451B2 !important; } - input[type=checkbox]:checked:active + .slider { - background-color: #3E63DD !important; - box-shadow: 0px 0px 0px 4px #C6D4F9 !important; + + input[type=checkbox]:checked:active+.slider { + background-color: #3E63DD !important; + box-shadow: 0px 0px 0px 4px #C6D4F9 !important; } } diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index eb45c34327..3f687a72dd 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -5,18 +5,20 @@ @import "./queryManager.scss"; @import "./onboarding.scss"; @import "./components.scss"; -@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,400;1,500;1,600;1,700&display=swap'); - @import "./global-datasources.scss"; - @import "./typography.scss"; @import "./designtheme.scss"; +@import "./dropdown-custom.scss"; +@import "./ui-operations.scss"; +@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,400;1,500;1,600;1,700&display=swap'); +@import 'react-loading-skeleton/dist/skeleton.css'; // variables $border-radius: 4px; + body { - font-family: "Roboto", sans-serif; + font-family: 'IBM Plex Sans'; } input, @@ -142,7 +144,7 @@ button { .editor { .header-container { max-width: 100%; - padding: 0 10px; + padding: 0 10px 0 0; min-height: 45px; } @@ -211,7 +213,7 @@ button { display: flex; min-width: 0; word-wrap: break-word; - background-color: #fff; + background-color: #ffffff; background-clip: border-box; border: 1px solid rgba(101, 109, 119, 0.16); border-radius: 4px; @@ -283,11 +285,12 @@ button { left: 0; overflow-x: hidden; flex: 1 1 auto; - background-color: #fff; + background-color: #fff !important; background-clip: border-box; border: solid rgba(0, 0, 0, 0.125); border-width: 0px 1px 3px 0px; margin-top: 0px; + padding-top: 0px; .accordion-item { border: solid rgba(101, 109, 119, 0.16); @@ -361,8 +364,8 @@ button { width: 300px; flex: 1 1 auto; top: 45px; - border-top: 1px solid #E6E8EB; - background-color: #fff; + border-top: 1px solid var(--slate7); + background-color: var(--base); background-clip: border-box; border: solid rgba(0, 0, 0, 0.125); border-width: 0px 0px 0px 1px; @@ -564,7 +567,6 @@ button { .canvas-container { scrollbar-width: auto; width: 100%; - // margin-left: 10%; } .canvas-container::-webkit-scrollbar { @@ -633,19 +635,32 @@ button { } .home-search-holder { - height: 36px; - width: 272px; + height: 20px; + width: 100%; + margin-top: 32px; .homepage-search { + background: transparent; + color: var(--slate9); + height: 20px; + &:focus { background: none; } } } +.homepage-app-card-list-item-wrap { + row-gap: 16px; + column-gap: 32px; + display: flex; + margin-top: 22px; +} + .homepage-app-card-list-item { - max-width: 290px; + max-width: 272px; flex-basis: 33%; + padding: 0 !important; } .homepage-dropdown-style { @@ -655,11 +670,12 @@ button { margin: 0; line-height: 1.4285714; width: 100%; - padding: 0.5rem 0.75rem; + padding: 0.5rem 0.75rem !important; font-weight: 400; white-space: nowrap; border: 0; cursor: pointer; + font-size: 12px; } .homepage-dropdown-style:hover { @@ -738,9 +754,13 @@ button { } button.create-new-app-button { - background-color: $primary-light; + background-color: var(--indigo9); + } + + + .app-list { .app-card { height: 180px; @@ -751,9 +771,9 @@ button { overflow: hidden; .app-creation-time { - font-size: 12px; - line-height: 12px; - color: #61656f; + span { + color: var(--slate11) !important; + } } .app-creator { @@ -768,7 +788,6 @@ button { .app-icon-main { background-color: $primary; - border-radius: 4px; .app-icon { img { @@ -780,6 +799,12 @@ button { } } + .app-template-card-wrapper { + .card-body { + padding-left: 0px !important; + } + } + .app-title { line-height: 20px; font-size: 1rem; @@ -803,10 +828,6 @@ button { .menu-ico { cursor: pointer; - // &__open { - // background-color: #d2ddec; - // } - img { padding: 0px; height: 14px; @@ -814,10 +835,6 @@ button { vertical-align: unset; } } - - // .menu-ico:hover { - // background-color: #d2ddec; - // } } .app-card.highlight { @@ -825,20 +842,65 @@ button { box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); border: 0.5px solid $primary; - button.edit-button { - background: #ffffff; - border: 1px solid $primary-light; + .edit-button { box-sizing: border-box; - border-radius: 4px; + border-radius: 6px; color: $primary-light; + width: 136px; + height: 28px; + background: var(--indigo11) !important; + border: none; + color: var(--indigo4); + + &:hover { + background: var(--indigo10); + + } + + &:focus { + box-shadow: 0px 0px 0px 4px var(--indigo6); + background: var(--indigo10); + outline: 0; + } + + + &:active { + background: var(--indigo11); + box-shadow: none; + } } - button.launch-button { - background: $primary-light; - border: 1px solid $primary-light; + .launch-button { + box-sizing: border-box; - border-radius: 4px; - color: #ffffff; + border-radius: 6px; + color: var(--base); + width: 92px; + height: 28px; + background: var(--base); + border: 1px solid var(--slate7); + color: var(--slate12); + + &:hover { + background: var(--slate8); + color: var(--slate11); + border: 1px solid var(--slate8); + background: var(--base); + } + + &:active { + background: var(--base); + box-shadow: none; + border: 1px solid var(--slate12); + color: var(--slate12); + } + + &:focus { + background: var(--base); + color: var(--slate11); + border: 1px solid var(--slate8); + box-shadow: 0px 0px 0px 4px var(--slate6); + } } .app-title { @@ -854,6 +916,12 @@ button { .template-library-modal { font-weight: 500; + .modal-header { + background-color: var(--base) !important; + border-bottom: 1px solid var(--slate5); + + } + .modal-dialog { max-width: 90%; height: 80%; @@ -862,9 +930,12 @@ button { height: 100%; padding: 0; + .modal-body { height: 100%; padding: 0 10px; + background-color: var(--base) !important; + .container-fluid { height: 100%; @@ -886,7 +957,6 @@ button { .template-categories { .list-group-item { border: 0; - // padding-bottom: 3px; } .list-group-item.active { @@ -899,7 +969,6 @@ button { .template-app-list { .list-group-item { border: 0; - // padding-bottom: 3px; } .list-group-item.active { @@ -993,7 +1062,7 @@ button { .template-list-column, .categories-column, .modal-header { - border-color: #232e3c !important; + border-color: var(--slate5) !important; } .modal-body, @@ -1008,7 +1077,6 @@ button { .list-group-item { color: white; border: 0; - // padding-bottom: 3px; } .list-group-item:hover { @@ -1026,14 +1094,11 @@ button { .list-group-item { border: 0; color: white; - // padding-bottom: 3px; } .list-group-item:hover { border: 0; - // color: red; background-color: #232e3c; - // padding-bottom: 3px; } .list-group-item.active { @@ -1042,7 +1107,7 @@ button { } .no-results-item { - background-color: #2b394a; + background-color: var(--slate4); color: white; } } @@ -1132,32 +1197,6 @@ button { .homepage-body { .app-list { - .app-card { - .app-creation-time { - color: #afb0b2; - } - - .app-creator { - color: #c3c4cb; - } - } - - .app-card.highlight { - background-color: #2c405c; - - button.edit-button { - background: transparent; - border: 1px solid #ffffff; - color: #ffffff; - } - - button.launch-button { - background: $primary-light; - border: 1px solid $primary-light; - color: #ffffff; - } - } - .app-title { line-height: 20px; font-size: 16px; @@ -1287,13 +1326,12 @@ button { font-weight: 400; line-height: 1.4285714; color: #232e3c; - background-color: #fff; + background-color: #ffffff; background-clip: padding-box; border: 1px solid #dadcde; -webkit-appearance: none; -moz-appearance: none; appearance: none; - // border-radius: 0; border-radius: $border-radius !important; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } @@ -1313,7 +1351,7 @@ button { * Options wrapper */ .select-search__select { - background: #fff; + background: #ffffff; box-shadow: 0 0.0625rem 0.125rem rgba(0, 0, 0, 0.15); } @@ -1340,7 +1378,7 @@ button { height: 36px; width: 100%; padding: 0 16px; - background: #fff; + background: var(--base); border: none; outline: none; font-family: "Roboto", sans-serif; @@ -1361,7 +1399,7 @@ button { .select-search__option.is-highlighted.is-selected, .select-search__option.is-selected:hover { background: #2eb378; - color: #fff; + color: #ffffff; } /** @@ -1486,11 +1524,11 @@ button { .select-search-dark__input { display: block; width: 100%; - padding: 0.4375rem 0.75rem; + // padding: 0.4375rem 0.75rem; font-size: 0.875rem; font-weight: 400; line-height: 1.4285714; - color: #fff; + color: #ffffff; background-color: #2b3547; background-clip: padding-box; border: 1px solid #232e3c; @@ -1536,9 +1574,8 @@ button { height: 36px; width: 100%; padding: 0 16px; - background-color: $dark-background !important; - color: #fff !important; - border: none; + background-color: var(--base) !important; + color: #ffffff !important; outline: none; font-family: "Roboto", sans-serif; font-size: 14px; @@ -1609,7 +1646,7 @@ button { } .select-search-dark:not(.select-search-dark--multiple) .select-search-dark__input:hover { - border-color: #fff; + border-color: #ffffff; } .select-search-dark:not(.select-search-dark--multiple) .select-search-dark__select { @@ -1853,7 +1890,7 @@ button { } .form-check-input:checked { - background-color: $primary; + background-color: var(--indigo9) !important; border-color: rgba(101, 109, 119, 0.24); } @@ -1880,10 +1917,6 @@ tr:focus { } } -// .jet-container { -// // width: 100%; -// } - .select-search__option { color: rgb(90, 89, 89); } @@ -2036,7 +2069,7 @@ tr:focus { padding: 5px; } -.table-filters { +.table-filters,.table-add-new-row { position: absolute; top: 2.85rem; width: 80%; @@ -2096,17 +2129,25 @@ tr:focus { #popover-app-menu { border-radius: 4px; width: 150px; - box-shadow: 0px 3px 2px rgba(0, 0, 0, 0.25); + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + background: var(--base); + color: var(--slate12); + border: 1px solid var(--slate3); + + .popover-arrow { + display: none; + } .popover-body { padding: 16px 12px 0px 12px; + color: var(--slate12); .field { font-weight: 500; font-size: 0.7rem; &__danger { - color: #ff6666; + color: var(--tomato9); } } } @@ -2144,7 +2185,7 @@ body { } .RichEditor-root { - background: #fff; + background: #ffffff; border: 1px solid #ddd; font-family: "Georgia", serif; font-size: 14px; @@ -2318,20 +2359,20 @@ body { .CalendarDay__selected_span:hover { background: $primary; border: 1px double $primary; - color: #fff; + color: #ffffff; } .CalendarDay__hovered_span:active, .CalendarDay__hovered_span:hover { background: $primary; border: 1px double $primary; - color: #fff; + color: #ffffff; } .CalendarDay__hovered_span { background: #83b8e7; border: 1px double #83b8e7; - color: #fff; + color: #ffffff; } .table-responsive { @@ -2456,7 +2497,7 @@ hr { font-size: 0.875rem; font-weight: 400; color: #232e3c; - background-color: #fff; + background-color: #ffffff; background-clip: padding-box; border: 1px solid #dadcde; -webkit-appearance: none; @@ -2485,7 +2526,7 @@ hr { font-size: 0.875rem; font-weight: 400; color: #232e3c; - background-color: #fff; + background-color: #ffffff; background-clip: padding-box; border: 1px solid #dadcde; border-radius: $border-radius; @@ -2516,7 +2557,7 @@ hr { .handle-content { cursor: move; - color: $white; + color: #ffffff; background: $primary; } @@ -2746,7 +2787,7 @@ input:focus-visible { .select-search__option.is-selected { background: $primary; - color: $white; + color: #ffffff; } } @@ -2757,7 +2798,7 @@ input:focus-visible { .widget-documentation-link { position: fixed; bottom: 0; - background: $white; + background: #ffffff; width: 100%; z-index: 1; } @@ -2859,31 +2900,21 @@ input:focus-visible { .theme-dark .navbar .navbar-nav .nav-link.active, .theme-dark .navbar .navbar-nav .nav-link.show, .theme-dark .navbar .navbar-nav .show>.nav-link { - color: #fff; + color: #ffffff; } - .form-check>.form-check-input:not(:checked) { - background-color: #fff; - } - - .form-switch>.form-check-input:not(:checked) { - background-color: #47505d !important; - } .form-check-label { color: white; } - .left-sidebar .active { - background: #333c48; - } .left-sidebar .left-sidebar-item { border-bottom: 1px solid #333c48; } .nav-tabs .nav-link.active { - color: #fff !important; + color: #ffffff !important; } .nav-tabs .nav-link { @@ -2891,7 +2922,7 @@ input:focus-visible { } .card-body> :last-child { - color: #fff !important; + color: #ffffff !important; } .form-control { @@ -2912,12 +2943,12 @@ input:focus-visible { .DateInput_input { background-color: #1f2936; - color: #fff; + color: #ffffff; } &.daterange-picker-widget { .DateRangePickerInput_arrow_svg { - fill: #fff; + fill: #ffffff; } } @@ -2985,7 +3016,7 @@ input:focus-visible { background-color: #1f2936; .text-muted { - color: #fff !important; + color: var(--slate09) !important; } } @@ -3012,7 +3043,7 @@ input:focus-visible { .query-list { .text-muted { - color: #fff !important; + color: #ffffff !important; } .mute-text { @@ -3020,7 +3051,6 @@ input:focus-visible { } } - .left-sidebar, .editor-sidebar { background-color: #1f2936 !important; } @@ -3035,7 +3065,7 @@ input:focus-visible { } .editor .editor-sidebar .nav-tabs .nav-link { - color: #fff; + color: #ffffff; img { filter: brightness(0) invert(1); @@ -3063,11 +3093,8 @@ input:focus-visible { } .left-sidebar { - border: solid rgba(255, 255, 255, 0.09); - border-width: 0px 1px 3px 0px; - .text-muted { - color: #fff !important; + color: #ffffff !important; } .left-sidebar-page-selector { @@ -3088,12 +3115,8 @@ input:focus-visible { } } - .folder-list { - color: #fff !important; - } - .app-title { - color: #fff !important; + color: var(--base) !important; } .RichEditor-root { @@ -3102,7 +3125,7 @@ input:focus-visible { } .app-description { - color: #fff !important; + color: #ffffff !important; } .btn-light, @@ -3138,7 +3161,7 @@ input:focus-visible { .app-users-list { .text-muted { - color: #fff !important; + color: #ffffff !important; } } @@ -3152,7 +3175,7 @@ input:focus-visible { border-width: 0px 0px 1px 0px !important; .text-muted { - color: #fff !important; + color: #ffffff !important; } } @@ -3168,11 +3191,6 @@ input:focus-visible { filter: brightness(0) invert(1); } - .launch-btn { - filter: brightness(0.4) !important; - background: #8d9095; - } - .badge { .svg-icon { filter: brightness(1) invert(0); @@ -3183,7 +3201,7 @@ input:focus-visible { background: transparent; .text-muted { - color: #fff !important; + color: #ffffff !important; } } @@ -3192,15 +3210,25 @@ input:focus-visible { border-width: 0px 0px 1px 0px !important; } + .home-page-content { + .hr-text { + color: var(--slate11) !important; + text-transform: lowercase !important; + font-weight: 400; + font-size: 12px; + line-height: 20px; + } + } + .hr-text { - color: #fff !important; + color: #ffffff !important; } .skeleton-line::after { background-image: linear-gradient(to right, - #566177 0, - #5a6170 40%, - #4c5b79 80%); + #121212 0, + #121212 40%, + #121212 80%); } .app-icon-skeleton::after { @@ -3224,7 +3252,7 @@ input:focus-visible { } .select-search__select { - background: #fff; + background: #ffffff; box-shadow: 0 0.0625rem 0.125rem rgba(0, 0, 0, 0.15); } @@ -3234,7 +3262,7 @@ input:focus-visible { .select-search__option, .select-search__not-found { - background: #fff; + background: #ffffff; } .select-search__option.is-highlighted, @@ -3245,21 +3273,21 @@ input:focus-visible { .select-search__option.is-highlighted.is-selected, .select-search__option.is-selected:hover { background: #2eb378; - color: #fff; + color: #ffffff; } .org-users-page { .user-email, .user-status { - filter: brightness(0) invert(1); + color: var(--slate12) !important; } } .org-users-page { .select-search__option.is-selected { background: $primary; - color: $white; + color: #ffffff; } .select-search__option:not(.is-selected):hover { @@ -3282,7 +3310,7 @@ input:focus-visible { .org-variables-page { .select-search__option.is-selected { background: $primary; - color: $white; + color: #ffffff; } .select-search__option:not(.is-selected):hover { @@ -3320,8 +3348,8 @@ input:focus-visible { .select-search:not(.is-loading):not(.select-search--multiple) .select-search__value::after { transform: rotate(45deg); - border-right: 1px solid #fff; - border-bottom: 1px solid #fff; + border-right: 1px solid #ffffff; + border-bottom: 1px solid #ffffff; } .widget-documentation-link { @@ -3383,7 +3411,7 @@ input:focus-visible { .theme-dark .markdown>table thead th, .theme-dark .table thead th { background: #1c252f; - color: #fff; + color: #ffffff; } .sketch-picker { @@ -3470,7 +3498,7 @@ input[type="text"] { --rmsc-border: #333333; --rmsc-gray: #555555; --rmsc-bg: #1f2936; - color: #fff; + color: #ffffff; } } @@ -3587,7 +3615,7 @@ input[type="text"] { } .nav-item { - background: #fff; + background: #ffffff; font-size: 14px; font-style: normal; font-weight: 400; @@ -3664,7 +3692,7 @@ input[type="text"] { line-height: 40px; margin-bottom: 16px; margin-top: 40px; - color: #000; + color: var(--slate12); font-family: Inter; } @@ -3679,7 +3707,7 @@ input[type="text"] { line-height: 20px; display: flex; align-items: center; - color: #687076; + color: var(--slate11) !important; } // template card styles @@ -3903,13 +3931,6 @@ input[type="text"] { .jet-listview { overflow-y: overlay; overflow-x: hidden; - - - // .rows { - // } - - // .list-item { - // } } .jet-listview::-webkit-scrollbar-track { @@ -4100,7 +4121,7 @@ input[type="text"] { [data-rb-event-key="close-inpector-light"] { position: absolute; right: -80px; - background-color: #fff !important; + background-color: #ffffff !important; width: 10% !important; } @@ -4176,7 +4197,7 @@ input[type="text"] { } .bg-primary-lt { - color: #fff !important; + color: #ffffff !important; background: #6383db !important; } @@ -4309,10 +4330,22 @@ input[type="text"] { input { width: 200px; border-radius: 5px !important; + color: var(--slate12) !important; + background-color: var(--base); + } + + .input-icon-addon { + min-width: 0rem !important; + } + + .input-icon .form-control:not(:first-child), + .input-icon .form-select:not(:last-child) { + padding-left: 28px !important; } input:focus { width: 300px; + background-color: var(--base); } .input-icon .input-icon-addon { @@ -4322,8 +4355,20 @@ input[type="text"] { .input-icon .input-icon-addon.end { pointer-events: auto; + .tj-common-search-input-clear-icon { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 4px; + // gap: 8px; + width: 20px; + height: 20px; + background: var(--indigo3) !important; + border-radius: 4px; + } + div { - background-color: #a6b6cc; border-radius: 12px; color: #ffffff; padding: 1px; @@ -4364,7 +4409,6 @@ input[type="text"] { * Folder List */ .folder-list { - color: #292d37; overflow-y: auto; .list-group-transparent .list-group-item.active { @@ -4386,29 +4430,47 @@ input[type="text"] { } .list-group-item.all-apps-link { - font-weight: 400; + display: flex; + align-items: center; + color: var(--slate12); + border-radius: 6px; + + &:active { + background: var(--indigo4); + } + + &:focus { + box-shadow: 0px 0px 0px 4px #DFE3E6; + } } .folder-info { - color: #8991a0; - font-size: 0.75rem; display: contents; + font-weight: 500 !important; + display: flex; + align-items: center; + letter-spacing: -0.02em; + text-transform: uppercase; + color: var(--slate9); } .folder-create-btn { - color: $primary; + width: 28px; + height: 28px; + background: var(--base); + border: 1px solid; + border-color: var(--slate7); cursor: pointer; + border-radius: 6px; + display: flex; + justify-content: center; + align-items: center; } .menu-ico { cursor: pointer; - // padding: 3px; border-radius: 13px; - // &__open { - // background-color: #d2ddec; - // } - img { padding: 0px; height: 14px; @@ -4416,10 +4478,6 @@ input[type="text"] { vertical-align: unset; } } - - // .menu-ico:hover { - // background-color: #d2ddec; - // } } /** @@ -4432,12 +4490,18 @@ input[type="text"] { .modal-content.home-modal-component { border-radius: 8px; overflow: hidden; - background-color: #fefeff; - color: #000000; + background-color: var(--base); + color: var(--slate12); + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + + .modal-header { + border-bottom: 1px solid var(--slate5) !important; + } .modal-header, .modal-body { padding: 16px 28px; + background: var(--base); } .modal-title { @@ -4445,17 +4509,9 @@ input[type="text"] { font-weight: 500; } - // .btn-close { - // width: 3.5rem; - // height: 2.5rem; - // } - - // .modal-body { - // padding-top: 0px; - // } - input { border-radius: 5px !important; + background: var(--base); } .modal-main { @@ -4470,11 +4526,30 @@ input[type="text"] { } } } - +.home-modal-component-editor.dark +{ + .modal-header,.modal-body{ + background-color: #232e3c; + color: #fff; + } + .form-control{ + color: #fff; + background-color: #232e3c !important; + } +} .onboarding-modal.dark .modal-content { @extend .modal-content.home-modal-component.dark; } - +.modal-content.home-modal-component.dark-theme { + .btn-close { + filter: brightness(0) invert(1); + } +} +.home-modal-component{ + .btn-close { + opacity: 1 !important; + } +} .modal-content.home-modal-component.dark { background-color: $bg-dark-light !important; color: $white !important; @@ -4540,7 +4615,7 @@ input[type="text"] { visibility: hidden; font-size: 12px; background-color: $black; - color: $white; + color: #ffffff; text-align: center; padding: 5px 10px; position: absolute; @@ -4573,6 +4648,10 @@ input[type="text"] { .icon-change-modal { ul { list-style-type: none; + margin: 0 auto; + text-align: center; + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; li { float: left; @@ -4609,7 +4688,7 @@ input[type="text"] { .animation-fade { animation-name: fade; animation-duration: 0.3s; - animation-timing-function: linear; + animation-timing-function: ease-in; } @keyframes fade { @@ -4724,8 +4803,6 @@ input[type="text"] { position: absolute; width: 12px; height: 12px; - // left: calc(50% - .5rem); - // top: calc(50% - .5rem); animation: spinner-border 0.75s linear infinite; } } @@ -4800,7 +4877,7 @@ div#driver-page-overlay { border-color: rgba(101, 109, 119, 0.16) !important; .driver-popover-title { - color: #fff !important; + color: var(--base) !important; } .driver-popover-tip { @@ -4812,7 +4889,7 @@ div#driver-page-overlay { } .driver-popover-footer .driver-close-btn { - color: #fff !important; + color: #ffffff !important; text-shadow: none !important; } @@ -4855,7 +4932,8 @@ div#driver-page-overlay { .popover.popover-dark-themed { background-color: $bg-dark-light; - border-color: rgba(101, 109, 119, 0.16); + border-color: rgba(101,109,119,0.16); + .popover-body { color: #d9dcde !important; @@ -4956,6 +5034,12 @@ div#driver-page-overlay { } } +.org-name { + color: var(--slate12) !important; + font-size: 12px; +} + + .organization-list { margin-top: 4px; @@ -5075,13 +5159,82 @@ div#driver-page-overlay { } } +.sso-button-footer-wrap { + display: flex !important; + justify-content: center; + width: 100%; +} + +.tj-icon { + cursor: pointer; +} + +#login-url, +#redirect-url { + margin-bottom: 0px !important; +} + +.git-encripted-label { + color: var(--green9); +} + +.card-header { + border-bottom: 1px solid var(--slate5) !important; +} + +.manage-sso-container { + position: relative; +} + +.sso-card-wrapper { + background: var(--base); + min-height: 100%; + height: calc(100vh - 156px) !important; + + display: grid; + grid-template-rows: auto 1fr auto; + + .card-header { + border-bottom: 1px solid var(--slate5) !important; + } + + .form-control { + background: var(--base); + } + + .sso-card-footer { + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + padding: 24px 32px; + gap: 8px; + width: 660px; + height: 88px; + border-top: 1px solid var(--slate5) !important; + background: var(--base); + margin-top: 0px !important; + } +} + // Left Menu .left-menu { - padding: 1rem 0.5rem; - border-radius: 5px; + background: var(--base); + + .tj-list-item { + gap: 40px; + width: 187px; + height: 32px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .folder-list-selected { + background-color: var(--indigo4); + } ul { - overflow: auto; margin: 0px; padding: 0px; @@ -5089,27 +5242,61 @@ div#driver-page-overlay { float: left; list-style: none; width: 100%; - padding: 5px 10px; - font-weight: bold; - border-radius: 5px; + padding: 6px 8px; + border-radius: 6px; cursor: pointer; margin: 3px 0px; + color: var(--base-black) !important; } li.active { background-color: $primary; - color: $white; + color: #ffffff; } li:not(.active):hover { - background-color: #d2daf0; + background: var(--slate4); + border-radius: 6px; } } } +.enabled-tag { + padding: 4px 16px; + gap: 10px; + width: 77px; + height: 28px; + background: var(--grass3); + border-radius: 100px; + color: var(--grass9); + font-weight: 500; +} + +.disabled-tag { + padding: 4px 16px; + gap: 10px; + color: var(--tomato9); + width: 81px; + height: 28px; + background: var(--tomato3); + border-radius: 100px; + font-weight: 500; +} + .manage-sso { .title-with-toggle { width: 100%; + font-weight: 500; + + .card-title { + color: var(--slate12) !important; + font-weight: 500; + } + + .form-check-input { + width: 28px; + height: 16px; + } input[type="checkbox"] { /* Double-sized Checkboxes */ @@ -5131,20 +5318,14 @@ div#driver-page-overlay { overflow: auto; div { - margin: 0px 0px 5px 0px; - background-color: #eaeaea; - float: left; - padding: 2px 10px; - font-size: 11px; - border-radius: 5px; - border: 1px solid #e1e1e1; + color: var(--slate11); + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 20px; } } -.theme-dark .help-text div { - background-color: $dark-background; - border: 1px solid $dark-background; -} .org-invite-or { padding: 1rem 0rem; @@ -5158,7 +5339,7 @@ div#driver-page-overlay { } h2 span { - background: #fff; + background: #ffffff; padding: 0 10px; } } @@ -5410,7 +5591,7 @@ div#driver-page-overlay { font-size: 12px; border-radius: 4px; color: rgba(0, 0, 0, .65); - background-color: #fff; + background-color: #ffffff; border: 1px solid #d9d9d9; min-width: 40px; width: auto !important; @@ -5783,7 +5964,7 @@ a.step-item-disabled { } .step-item.active:before { - background: #fff !important; + background: #ffffff !important; } .steps .step-item.active:before { @@ -5817,6 +5998,7 @@ a.step-item-disabled { .notification-center { max-height: 500px; overflow: auto; + margin-left: 11px !important; .empty { padding: 0 !important; @@ -5828,6 +6010,14 @@ a.step-item-disabled { .card { min-width: 400px; + background: var(--base); + color: var(--slate12); + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + } + + .card-footer { + background: var(--base); + color: var(--slate12); } .spinner { @@ -5835,8 +6025,6 @@ a.step-item-disabled { } } -// steps-widget end - // profile-settings css .confirm-input { padding-right: 8px !important; @@ -5893,7 +6081,7 @@ input.hide-input-arrows { .icon-box { padding: 7px 5px 7px 2px; - color: #fff; + color: #ffffff; .icon { stroke-width: 4.5px; @@ -5901,10 +6089,10 @@ input.hide-input-arrows { } .tick-box { - border: 3px solid $primary; + border: 3px solid var(--indigo9); .icon-box { - background: $primary; + background: var(--indigo9); } } @@ -5928,7 +6116,7 @@ input.hide-input-arrows { .popover-header { padding-bottom: 0; - background-color: #fff; + background-color: #ffffff; .input-icon { margin-bottom: 0.5rem !important; @@ -6012,8 +6200,8 @@ input.hide-input-arrows { } .list-group-item.active { - background-color: #edf1ff; - color: #4d72fa; + background-color: var(--indigo4); + color: var(--slate12); font-weight: 600; margin-top: 0px; } @@ -6219,26 +6407,51 @@ input.hide-input-arrows { } } -.user-group-container-wrap, -.workspace-variable-container-wrap { - margin-top: 1.25rem; +.user-group-container-wrap { + margin: 20px auto 0 auto; } .dragged-column { z-index: 1001; } +#storage-sort-popover { + max-width: 800px; + width: 800px; + background-color: var(--base); + box-sizing: border-box; + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + border-radius: 4px; + border: 1px solid var(--slate3) !important; + left: 109px !important; + top: 8px !important; + position: absolute !important; + + + .card-body, + .card-footer { + background: var(--base); + } +} + + #storage-filter-popover { max-width: 800px; width: 800px; - background-color: $white; + background-color: var(--base); box-sizing: border-box; - box-shadow: 0px 2px 4px rgba(40, 57, 72, 0.06), 0px 4px 6px rgba(40, 57, 72, 0.1); + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); border-radius: 4px; -} + border: 1px solid var(--slate3) !important; + left: 193px !important; + top: 10px !important; + position: absolute !important; -#storage-filter-popover.theme-dark { - background-color: #22272E; + + .card-body, + .card-footer { + background: var(--base); + } } // Table set to full width @@ -6420,7 +6633,7 @@ tbody { display: flex; justify-content: flex-start; padding: 0.75rem 0.25rem; - border: 1px solid $border-grey-light; + border: 1px solid var(--slate7); } } @@ -6434,12 +6647,12 @@ tbody { } .dropdown-table-column-hide { - background-color: #fff; + background-color: #ffffff; box-shadow: 0 0 0 2px #0000001a; } .dropdown-table-column-hide-dark-themed { - color: #fff !important; + color: #ffffff !important; background-color: #1f2936 !important; box-shadow: 0 0 0 2px #9292921a; } @@ -6493,9 +6706,9 @@ tbody { } .add-more-columns-btn { - background: #F0F4FF; + background: var(--indigo3); font-weight: 500; - color: #3E63DD; + color: var(--indigo9); font-size: 12px; border-radius: 600; } @@ -6704,6 +6917,10 @@ tbody { } } +.subheader { + margin-bottom: 12px; +} + .theme-dark { .layout-sidebar-icon { &:hover { @@ -6712,14 +6929,10 @@ tbody { } .tooljet-database { - .btn { - background-color: #273342; - color: #fff; - } .table-name, .subheader { - color: #fff; + color: var(--slate9); } .list-group-item.active { @@ -6774,25 +6987,6 @@ tbody { background-color: #1F2936; } } - - .app-icon-main { - background: #2F3C4C !important; - } - - .launch-button { - svg { - path { - fill: white !important; - } - } - } - - .breadcrumb-item { - a { - - color: white !important; - } - } } :root { @@ -6803,8 +6997,6 @@ tbody { .application-brand { position: relative; display: flex; - padding: 6px; - margin-bottom: 6px; justify-content: center; } @@ -6814,61 +7006,105 @@ tbody { } .app-icon-main { - background: #F0F4FF; + background: var(--indigo3) !important; + border-radius: 6px !important; + display: flex; + justify-content: center; + align-items: center; + width: 48px; + height: 48px; } .user-avatar-nav-item, .audit-log-nav-item, .notification-center-nav-item { border-radius: 4px; - position: fixed; - bottom: 8px; - left: 8px; } .audit-log-nav-item { bottom: 40px; } -.notification-center-nav-item { - bottom: 60px; +.workspace-content-wrapper, +.database-page-content-wrap { + background: var(--slate2); } +.workspace-variable-table-card { + margin: 0 auto; + width: 880px; +} .organization-page-sidebar { - height: calc(100vh - 48px); + height: calc(100vh - 64px); max-width: 288px; + background-color: var(--base); + border-right: 1px solid var(--slate5) !important; + display: grid !important; + grid-template-rows: auto 1fr auto !important; } .home-page-sidebar { max-width: 288px; + background-color: var(--base); + border-right: 1px solid var(--slate5); + display: grid; + grid-template-rows: auto 1fr auto; +} +.empty-home-page-image { + margin-top: 14px; +} + +.create-new-table-btn { + width: 248px; + + button { + height: 40px !important; + + } } .tooljet-database-sidebar { max-width: 288px; + background: var(--base); + border-right: 1px solid var(--slate5); + .sidebar-container { - padding: 24px; - height: 124px !important; + height: 40px !important; + padding-top: 1px !important; + margin: 0 auto; + display: flex; + justify-content: center; } } -.all-apps-link { - font-weight: 400; - font-size: 14px; - height: 32px; +.create-new-app-dropdown { + width: 248px !important; + + + .dropdown-toggle-split { + border-left: 1px solid var(--indigo11) !important; + } + + button { + background-color: var(--indigo9) !important; + } } .create-new-app-button { - // max-width: 195px; font-weight: 500; font-size: 14px; - height: 32px; + height: 40px; + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; } .create-new-app-button+.dropdown-toggle { - height: 32px; + height: 40px; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; } .custom-select { @@ -7136,7 +7372,10 @@ tbody { } .app-creation-time { - font-size: 12px; + color: var(--slate11) !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .font-weight-400 { @@ -7167,24 +7406,10 @@ tbody { .layout-header { position: fixed; right: 0; - left: 48px; + left: 56px; z-index: 1; - background: #ffff; -} - -.theme-dark { - .layout-header { - background: #1f2936 !important; - } -} - -.organization-selector { - max-width: 288px; -} - -.organization-avatar { - max-width: 34px; - margin-right: 10px; + background: var(--base); + height: 64px; } .layout-sidebar-icon { @@ -7201,21 +7426,19 @@ tbody { display: none; } -.organization-selector { +.tj-dashboard-section-header { max-width: 288px; - max-height: 48px; - padding: 8px 16px; -} - -.organization-avatar { - max-width: 34px; - margin-right: 10px; + max-height: 64px; + padding-top: 20px; + padding-left: 20px; + padding-bottom: 24px; + border-right: 1px solid var(--slate5); } .layout-sidebar-icon { &:hover { background: #ECEEF0; - border-radius: 6px; + border-radius: 4px; } &:focus { @@ -7231,6 +7454,21 @@ tbody { display: block; } +.folder-list-group-item { + &:hover { + background: #ECEEF0; + } + + &:active { + background: var(--indigo4); + } + + &:focus { + box-shadow: 0px 0px 0px 4px #DFE3E6; + } +} + + .app-versions-selector { display: inline-flex; align-items: center; @@ -7254,23 +7492,22 @@ tbody { line-height: 20px; } -.app-version-delete { - display: none; -} .custom-version-selector__option:hover .app-version-delete { display: block; } .editor .editor-sidebar { - border-top: 1px solid #E6E8EB; + border-top: 1px solid var(--slate7); } .editor .navbar-brand { - border-right: 1px solid #E6E8EB; - padding-right: 12px !important; - padding-left: 4px; + border-right: 1px solid var(--slate7); padding-bottom: 1rem; + border-right: 1px solid var(--slate7); + width: 48px; + display: flex; + justify-content: center; } .theme-dark { @@ -7284,7 +7521,6 @@ tbody { } .modal-backdrop { - // background-color: hsla(0, 0%, 0%, 0.439); opacity: 0.5; } @@ -7303,7 +7539,12 @@ tbody { position: fixed; bottom: 0px; right: 0; - left: 336px; //48+288 + left: 344px; +} + +.home-page-footer { + height: 52px; + background-color: var(--base) !important; } .pagination-container { @@ -7319,9 +7560,30 @@ tbody { } } -.profile-card.dark.card { - background-color: #1F2936; - color: #C0C8CC; +.profile-card { + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + border-radius: 6px; + padding: 4px 0px; + width: 84px; + height: 86px; + margin-left: 10px; + background-color: var(--base); + + .dropdown-item { + width: 84px; + height: 36px; + min-width: 84px !important; + } + + svg { + margin-left: 2px; + } + + a { + span { + margin-left: 4px; + } + } } .theme-dark { @@ -7372,20 +7634,20 @@ tbody { // DASHBOARD SCROLL STYLES---> .create-new-app-wrapper { - height: 64px; + margin: 0 auto; + display: flex; + justify-content: center; + padding-top: 4px; } .home-page-sidebar { - height: calc(100vh - 48px) !important; -} - -.folder-list { - height: calc(100vh - 112px) !important; // 48px [navbar] + 64px [ all apps ] + height: calc(100vh - 64px) !important; //64 is navbar height } .home-page-content { - height: calc(100vh - 48px) !important; - overflow-y: scroll; + height: calc(100vh - 64px) !important; + overflow-y: auto; + background: var(--slate2); } .application-folders-list { @@ -7396,20 +7658,77 @@ tbody { // TABLE .table-left-sidebar { - height: calc(100vh - 173px) !important; // 48px [navbar] + 124px [ add table and search ] + extra 1 px + height: calc(100vh - 104px) !important; // 62px [navbar] + 40px [ add table and search ] + extra 2 px(border) overflow-y: auto; } -.database-table-header-wrapper { - height: 96px !important; -} - .toojet-db-table-footer { height: 52px; + background: var(--base) !important; +} + +.home-app-card-header { + margin-bottom: 32px; } .homepage-app-card { - height: 188px; + height: 166px; + outline: 1px solid var(--slate3); + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-radius: 6px; + padding: 16px; + background-color: var(--base) !important; + + .appcard-buttons-wrap { + display: none; + } + + .home-app-card-header { + .menu-ico { + display: none !important; + } + } + + &:hover { + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + + .home-app-card-header { + margin-bottom: 12px; + + .menu-ico { + display: block !important; + } + } + + .app-creation-time-container { + margin-bottom: 0px; + } + + .app-card-name { + margin-bottom: 0px; + } + + .app-creation-time { + display: none; + } + + + .appcard-buttons-wrap { + display: flex; + padding: 0px; + gap: 12px; + width: 240px; + height: 28px; + flex-direction: row; + + } + + .app-icon-main { + width: 36px; + height: 36px; + + } + } } .app-creation-time-container { @@ -7452,9 +7771,13 @@ tbody { } } -.table-list-items#popover-contained:has(.theme-dark) { - border-color: inherit; - overflow: hidden; +.table-list-items#popover-contained { + .popover-body { + outline: 1px solid var(--slate3); + background: var(--base); + overflow: hidden; + } + } .table-list-item-popover.dark { @@ -7473,24 +7796,24 @@ tbody { } @keyframes up-and-down { - to{ - opacity: 0.2; - transform: translateY(-20px); - + to { + opacity: 0.2; + transform: translateY(-20px); + } } -.spin-loader { +.spin-loader { position: fixed; width: 100%; - - .load{ + + .load { display: flex; justify-content: center; margin: 200px auto; } - .load div{ + .load div { width: 20px; height: 20px; background-color: #3E63DD; @@ -7502,11 +7825,11 @@ tbody { animation-direction: alternate; } - .load .two{ + .load .two { animation-delay: 0.3s; } - .load .three{ + .load .three { animation-delay: 0.6s; } } @@ -7514,13 +7837,13 @@ tbody { .organization-switch-modal { font-family: 'IBM Plex Sans'; - .modal-dialog{ + .modal-dialog { width: 376px; } - .modal-content{ + .modal-content { background: linear-gradient(0deg, #FFFFFF, #FFFFFF), - linear-gradient(0deg, #DFE3E6, #DFE3E6); + linear-gradient(0deg, #DFE3E6, #DFE3E6); } .modal-header { @@ -7541,7 +7864,7 @@ tbody { font-weight: 400; font-size: 14px; line-height: 20px; - color:#687076; + color: #687076; text-align: Center; margin-bottom: 0px; } @@ -7549,12 +7872,12 @@ tbody { .modal-body { padding: 18px 32px; - + .org-list { display: flex; flex-direction: column; - .org-item{ + .org-item { height: 50px; display: flex; align-items: center; @@ -7607,9 +7930,11 @@ tbody { } .organization-switch-modal.dark-mode { + .modal-footer, .modal-header { border-color: #232e3c !important; + p { color: rgba(255, 255, 255, 0.5) !important; } @@ -7628,8 +7953,8 @@ tbody { } - .modal-body { - .org-list{ + .modal-body { + .org-list { span { color: white; } @@ -7640,6 +7965,7 @@ tbody { } } } + .datasources-category { color: var(--slate10); } @@ -7648,15 +7974,1530 @@ tbody { font-size: .765625rem !important; } +.add-new-workspace-icon-wrap { + display: flex; + flex-direction: row; + align-items: center; + padding: 8px; + width: 34px; + height: 34px; + background: var(--indigo3); + border-radius: 6px; +} + +.add-new-workspace-icon-old-wrap { + display: none; +} + +.add-workspace-button { + padding: 8px 12px; + gap: 11px; + height: 50px; + + &:hover { + background: var(--indigo3); + margin: 0 auto; + border-radius: 6px; + padding-bottom: 10px; + + .add-new-workspace-icon-old-wrap { + padding: 8px; + width: 34px; + height: 34px; + background: var(--indigo9); + border-radius: 6px; + display: flex; + justify-content: center; + align-items: center; + + } + + .add-new-workspace-icon-wrap { + display: none; + + } + } + +} + +.tj-folder-list { + display: flex; + align-items: center; + color: var(—-slate12) !important; +} + +.app-card-name { + color: var(—-slate12); + margin-bottom: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.dashboard-breadcrumb-header { + display: flex; + align-items: center; +} + +.tj-version { + margin-right: 44px; + display: flex; + align-items: center; + color: var(--slate9); + +} + +.folder-list { + color: var(—-slate9) !important; +} + +.tj-folder-header { + margin-bottom: 12px; + height: 37px; + cursor: pointer; +} + +.tj-dashboard-header-title-wrap { + display: flex; + justify-content: center; + align-items: center; + color: var(--slate11); + + a { + text-decoration: none; + } +} + +.theme-dark { + .tj-onboarding-phone-input-wrapper { + .flag-dropdown { + background-color: #1f2936 !important; + + .country-list { + background-color: #1f2936 !important; + background: #1f2936; + + li { + .country .highlight { + background-color: #3a3f42; + color: #000 !important; + + div { + .country-name { + color: #6b6b6b !important; + } + } + + } + + &:hover { + background-color: #2b2f31; + } + + } + } + } + + } + + .react-tel-input .country-list .country.highlight { + color: #6b6b6b; + } +} + +.dashboard-breadcrumb-header-name { + font-weight: 500 !important; + color: var(—-slate12) !important; +} + +.tj-dashboard-header-wrap { + padding-top: 22px; + padding-bottom: 22px; + padding-left: 40px; + height: 64px; + border-bottom: 1px solid var(--slate5); +} + +.dashboard-breadcrumb-header-name:hover { + text-decoration: none !important; +} + + +.tj-avatar { + border-radius: 6px; + width: 36px; + height: 36px; + display: flex; + justify-content: center; + align-items: center; + background-color: var(--slate3) !important; + color: var(--slate11) !important; + text-transform: uppercase; + font-weight: 500; + + &:hover { + background-color: var(--slate4); + } + + &:focus { + box-shadow: 0px 0px 0px 4px var(--indigo6); + outline: 0; + } + + &:active { + box-shadow: none; + } +} + +.tj-current-org { + span { + color: var(--slate12); + + } +} + + +.sidebar-inner { + align-items: center; +} + +.workspace-drawer-wrap { + background: var(--base); +} + +.theme-dark { + .drawer-wrap { + background: var(--base); + } +} + +.users-table { + background: var(--base); + padding: 16px; + width: 848px; + margin: 0 auto; + padding: 16px; + + tbody { + + tr>td>span, + tr>td>a { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 140px; + } + } + + thead { + tr { + padding: 0px 6px; + gap: 90px; + width: 848px; + height: 40px; + display: flex; + align-items: center; + margin-top: 6px; + } + + tr>th { + background: var(--base) !important; + border-bottom: none !important; + padding: 0 !important; + width: 282px; + } + } + + tr { + background: var(--base); + height: 66px; + padding: 13px 0px; + border-bottom: 1px solid var(--slate7); + display: flex; + justify-content: space-between; + } + + tr>td { + border-bottom-width: 0px !important; + display: flex; + align-items: center; + flex: 16%; + padding-left: 0px !important; + padding-right: 0px !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +} + +.tj-input { + padding: 6px 10px; + gap: 17px; + width: 161.25px; + height: 32px; + background: var(--base); + border: 1px solid var(--slate7); + border-radius: 6px; + + ::placeholder { + color: var(--slate9) !important; + } + +} + +.workspace-setting-buttons-wrap { + display: flex; + gap: 12px; +} + +.workspace-settings-table-wrap { + max-width: 880px; + margin: 0 auto; +} + +.workspace-settings-filters { + display: flex; + gap: 12px; + flex-direction: row; + align-items: center; + position: relative; +} + +.workspace-setting-table-wrapper { + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + outline: 1px solid var(--slate7); + background: var(--base); + width: 880px; + margin: 0 auto; + border-radius: 6px; + height: calc(100vh - 223px); + position: relative; + +} + +.workspace-filter-text { + color: var(--slate11); + margin-bottom: 14px; +} + +.singleuser-btn { + padding: 6px 16px; + gap: 6px; + width: 152px; + height: 32px; + border-radius: 6px; + +} + +.multiuser-btn { + padding: 6px 16px; + gap: 6px; + width: 189px; + height: 32px; + border-radius: 6px; + +} + +.workspace-page-header { + width: 880px; + margin: 0 auto !important; + + div:first-child { + margin: 0 auto !important; + width: 880px; + + } +} + +.workspace-user-archive-btn { + width: 95px; + height: 28px; +} + +.workspace-clear-filter { + margin-left: 8px; + color: var(--indigo9); + font-weight: 600 !important; +} + +.workspace-clear-filter-wrap { + display: flex; + align-items: center; + width: 130px; + justify-content: flex-end; + position: absolute; + right: 16px; +} + +.tj-checkbox { + border-color: var(--slate7); +} + +.workspace-clipboard-wrap { + display: flex; + align-items: center; + width: 162.67px; + cursor: pointer; + + p { + font-weight: 500 !important; + margin-left: 5px; + } + + span { + display: flex; + align-items: center; + } +} + +.workspace-user-status { + margin-right: 22px; + margin-left: 5px; +} + +.worskpace-setting-table-gap { + margin-top: 20px; +} + +.tj-active { + background: #46A758; +} + +.tj-invited { + background: #FFB224; +} + +.tj-archive { + background: #E54D2E; +} + +.liner { + height: 1px; + background: var(--slate5); + width: 880px; + margin-top: 22px; +} + +.edit-button { + width: 135px; + height: 28px; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 4px 16px; + gap: 6px; + width: 135px; + height: 28px; +} + +.launch-button { + display: flex; + width: 93px; + height: 28px; + padding: 4px 16px; + gap: 6px; + align-items: center; + color: var(--slate12); + justify-content: center; +} +.launch-button.tj-disabled-btn{ + cursor: not-allowed; +} + +.breadcrumb-item { + a { + text-decoration: none !important; + color: var(--slate12); + } +} + +.table-list-item { + width: 248px; +} + +.workspace-settings-filter-items { + width: 161.25px; + + .css-13mf2tf-control { + width: 161.25px !important; + + } + + .css-10lvx9i-Input { + margin: 0 !important; + padding: 0 !important; + } + + .css-1bugkci-control, + .css-42vs31, + .css-ob45yj-menu { + background-color: var(--base) !important; + width: 161.25px !important; + } + + .css-6t9fnh-control { + border: 1px solid var(--slate7) !important; + background: var(--base); + color: var(--slate9); + width: 161.25px; + height: 32px; + + .css-1opnhvy-singleValue { + color: var(--slate9) !important; + + } + } + + input.tj-checkbox { + background: var(--base) !important; + color: var(--slate9); + border: 1px solid var(--slate7) !important; + + ::placeholder { + color: var(--slate9); + } + } +} + + +.tj-db-dataype { + margin-left: 8px; + color: var(--slate11); +} + +.tj-database-column-header { + color: var(--slate12); + padding: 4px 4px 4px 8px !important; + text-transform: capitalize !important; + line-height: 0px !important; + font-weight: 500 !important; + font-size: 12px !important; + line-height: 20px !important; + color: var(--slate12) !important; + + &:first-child { + display: flex !important; + align-items: center !important; + padding-left: 1rem !important; + } + +} + +.tj-database-column-row { + margin: 0; + + th:first-child { + height: 28px; + } + + th:first-child>div { + height: 16px; + width: 16px; + display: flex; + align-items: center; + height: 28px; + + input { + border-radius: 4px; + } + + } +} + +.tj-db-operaions-header { + height: 48px; + padding: 0 !important; + display: flex; + align-items: center; + background-color: var(--base); + + .row { + margin-left: 0px; + } + + .col { + padding-left: 0px; + display: flex; + gap: 8px; + align-items: center; + } +} + +.add-new-column-btn { + margin-left: 16px; + width: 144px !important; + height: 28px; + border-radius: 6px; + padding: 0 !important; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + color: var(--slate12); + border: none; +} + +.tj-db-filter-btn { + width: 81px; + height: 28px; + border-radius: 6px; + background: transparent; + color: var(--slate12); + border: none; + display: flex; + align-items: center; + justify-content: center; +} + +.tj-db-filter-btn-applied, +.tj-db-sort-btn-applied { + display: flex !important; + flex-direction: row !important; + justify-content: center !important; + align-items: center !important; + padding: 4px 16px !important; + width: 171px !important; + height: 28px !important; + background: var(--grass2) !important; + border-radius: 6px !important; +} + +.tj-db-filter-btn-active, +.tj-db-sort-btn-active { + width: 81px !important; + height: 28px !important; + background: var(--indigo4) !important; + border: 1px solid var(--indigo9) !important; + border-radius: 6px !important; + justify-content: center; + color: var(--indigo9) !important; +} + +.tj-db-header-add-new-row-btn { + width: 125px; + height: 28px; + background: var(--indigo3); + border-radius: 6px !important; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + gap: 6px; + border: none; + + span { + color: var(--indigo9); + } +} + +.tj-db-sort-btn { + width: 75px; + height: 28px; + background: transparent; + color: var(--slate12); + border: none; + display: flex; + align-items: center; + justify-content: center; +} + +.edit-row-btn { + background: transparent; + color: var(--slate12); + border: none; + display: flex; + align-items: center; +} + +.workspace-variable-header { + width: 880px; + justify-content: end; + margin: 0 auto; + display: flex; + padding: 0; +} + +.add-new-variables-button { + margin-bottom: 20px; + width: 169px; + height: 32px; +} + +.org-users-page-sidebar, +.left-menu { + padding: 16px; + gap: 7px; + width: 220px; + border-right: 1px solid var(--slate5); + overflow-y: auto; + overflow-x: hidden; +} + +.groups-header-wrap { + display: flex; + height: 36px; + border-bottom: 1px solid var(--slate5); +} + +.org-users-page-container { + width: 880px; + margin: 0 auto; +} + +.groups-main-header-wrap { + padding: 20px 0px 8px; + gap: 10px; + width: 612px; + height: 56px; + margin: 0 auto; + display: flex; + justify-content: space-between; + + p { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .nav-tabs .nav-link.active { + border-bottom: 2px solid var(--indigo9) !important; + } +} + +.form-check-input:disabled { + background-color: var(--slate8) !important; +} + +.manage-groups-body { + padding: 24px; + font-size: 12px; + overflow-y: auto; + height: calc(100vh - 300px); + +} + +.groups-sub-header-wrap { + width: 612px; + height: 36px; + border-bottom: 1px solid var(--slate5) !important; + + .nav-link.active { + border-bottom: 2px solid var(--indigo9) !important; + border-color: var(--indigo9) !important; + } + + .nav-item { + font-weight: 500 !important; + font-size: 12px !important; + } + + + p { + width: 205px; + } +} + +.groups-btn-container { + width: 880px; + justify-content: space-between; + margin: 0 auto; + margin-bottom: 20px; + height: 32px; + align-items: center; + +} + +.org-users-page { + margin: 0 auto; +} + +.org-users-page-card-wrap { + height: calc(100vh - 208px); +} + +.org-users-page-card-wrap, +.manage-sso-wrapper-card { + display: flex; + flex-direction: row; + background: var(--base); + width: 880px; + outline: 1px solid var(--slate5); + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-radius: 6px; +} + +.manage-sso-wrapper-card { + margin: 0 auto; + + .card-body { + overflow-y: auto; + padding: 40px; + } + + .card-header { + padding: 0px 24px; + width: 660px; + height: 72px; + border-bottom: 1px solid var(--slate5); + + } + + .form-check { + margin-bottom: 0px !important; + line-height: 24px; + font-size: 16px; + } +} + +.groups-sidebar-nav { + display: flex; + flex-direction: row; + align-items: center; + padding: 6px 8px; + gap: 40px; + width: 188px; + height: 32px; + background: var(--base); + border-radius: 6px; + cursor: pointer; +} + +.org-users-page-card-body { + width: 660px; +} + +.org-users-page { + .nav-tabs .nav-link.active { + background-color: transparent !important; + } + + .nav-tabs .nav-item.show .nav-link, + .nav-tabs .nav-link.active { + border-color: var(--indigo9) !important; + + } + + .nav-link:hover { + border-right: none !important; + border-left: none !important; + border-top: none !important; + + color: var(--indigo9); + } +} + +.groups-selected-row { + background-color: var(--indigo4); +} + +.add-apps-btn { + width: 160px; + height: 32px; +} + +.groups-app-body-header { + border-bottom: 1px solid var(--slate5); + + p { + height: 36px; + display: flex; + align-items: center; + width: 286px; + color: var(--slate11); + + } + + p:first-child { + width: 205px !important; + margin-left: 12px; + } + +} + +.manage-group-tab-icons { + margin-right: 6px; +} + +.manage-groups-no-apps-wrap { + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + width: 602px; + + p { + margin-top: 12px; + } + + span { + color: var(--slate11); + margin-top: 4px; + } + + div { + width: 64px; + height: 64px; + background: var(--indigo3); + border-radius: 12px; + display: flex; + justify-content: center; + align-items: center; + margin-top: 88px; + } +} + +.apps-permission-wrap { + height: 72px; + justify-content: center; + gap: 12px; +} + +.apps-folder-permission-wrap, +.apps--variable-permission-wrap { + height: 44px; +} + +.manage-group-permision-header { + border-bottom: 1px solid var(--slate5); + display: flex; + + p { + padding: 8px 12px; + gap: 10px; + width: 206px; + height: 36px; + font-weight: 500; + color: var(--slate11) !important; + } + +} + +.permission-body { + .form-check { + margin-bottom: 0px !important; + } + + tr { + border-bottom: 1px solid var(--slate5); + width: 612px !important; + + } + + td { + font-size: 12px; + font-weight: 500; + line-height: 20px; + letter-spacing: 0em; + text-align: left; + width: 206px !important; + padding-left: 12px; + + div { + padding-left: 12px; + } + } +} + + +.default-option-text { + margin-left: 10px; + margin-right: 16px; + font-size: 11px !important; +} + +.git-sso-help-text { + color: var(--slate11); +} + +.default-group-wrap { + gap: 10px; + width: 119px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: var(--grass3); + border-radius: 100px; +} + +.sso-icon-wrapper { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 8px 8px 8px 16px; + width: 251px; + height: 56px; + background: var(--slate3); + border-radius: 6px; + margin-top: 12px; +} + +.sso-main-box { + justify-content: center; + background: var(--slate6); + padding: 8px 16px; + width: 96px; + height: 40px; + border-radius: 6px; +} + +.default-danger-tag-wrap { + gap: 10px; + width: 113px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: var(--tomato6); + border-radius: 100px; + margin-bottom: 16px; +} + +.manage-group-users-info { + height: 48px; + width: 612px; + border-radius: 6px; + padding: 12px 24px 12px 24px; + background: var(--slate3); + border: 1px solid var(--slate5); + border-radius: 6px; + margin-bottom: 16px; + + p { + color: var(--slate12); + gap: 14px; + display: flex; + align-items: center; + + } +} + +.name-avatar { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + width: 36px; + height: 36px; + background-color: var(--slate3) !important; + border-radius: 6px; + color: var(--slate11); + margin-right: 12px; + text-transform: capitalize; +} + +.manage-group-users-row { + display: flex; + flex-direction: row; + align-items: baseline; + padding: 12px 6px; + width: 612px !important; + height: 64px; + border-bottom: 1px solid var(--slate5); + + p { + width: 272px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + span { + max-width: 150px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + + &:hover .apps-remove-btn { + display: flex; + } +} + +.manage-group-app-table-body { + width: 602px !important; + + tr { + display: flex; + font-family: 'IBM Plex Sans'; + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 20px; + color: var(--slate12); + } +} + +.apps-view-edit-wrap { + display: flex; + flex-direction: column; + width: 51px; + margin-right: 32px; +} + +.apps-table-row { + display: grid !important; + grid-template-columns: 205px 286px 115px; + + td { + padding: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &:hover .apps-remove-btn { + display: flex; + } +} + +.apps-remove-btn { + width: 97px; + height: 28px; + font-weight: 600 !important; +} + +.faded-text { + color: var(--slate8); +} + +.manage-groups-app-dropdown { + width: 440px; +} + +.create-new-group-button { + width: 169px; + height: 32px; + border-radius: 6px; +} + +.faded-input { + background: var(--slate5); +} + +.manage-group-table-head { + display: flex; + border-bottom: 1px solid var(--slate5); + width: 612px; + height: 36px; + padding: 8px 12px; + align-items: center; + + + p { + width: 272px !important; + color: var(--slate11); + font-weight: 500; + } + +} + +.manage-groups-permission-apps { + border-bottom: 1px solid var(--slate5); +} + +.manage-groups-permission-apps, +.apps-folder-permission-wrap, +.apps-variable-permission-wrap { + display: flex; + align-items: center; + padding: 12px; + gap: 10px; + + div { + width: 206px; + } +} + +.manage-groups-permission-apps, +.apps-variable-permission-wrap { + gap: 10px; + height: 72px; +} + +.apps-folder-permission-wrap { + height: 44px; + border-bottom: 1px solid var(--slate5); +} + +.delete-group { + text-decoration: none !important; + color: var(--tomato9) !important; +} + +.delete-link, +.remove-decoration { + text-decoration: none !important; +} + +.edit-group { + text-decoration: none !important; + color: var(--slate12) !important; +} + +.removed-decoration { + text-decoration: none !important; +} + +.rmsc .select-item.selected { + color: var(--slate12) !important; + background-color: var(--base) !important; +} + +.manage-groups-app-dropdown { + margin-right: 12px; + + .rmsc .dropdown-container:focus-within { + border: 1px solid var(--indigo9) !important; + box-shadow: 0px 0px 0px 2px #C6D4F9 !important; + } + + .dropdown-heading-value { + span { + color: var(--slate12) !important; + + } + } + + .multi-select { + .dropdown-container { + gap: 17px; + width: 440px; + height: 32px; + background: var(--base); + border: 1px solid var(--slate7); + border-radius: 6px; + display: flex; + justify-content: center; + align-items: center; + margin-right: 12px; + } + + } + + .dropdown-content { + .panel-content { + background: var(--base); + border: 1px solid var(--slate3); + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + border-radius: 6px; + + .select-panel { + .search { + border-bottom: 1px solid var(--slate5); + } + + .search, + input { + background-color: var(--base) !important; + } + } + + input[type='checkbox'] { + border: 1px solid red !important; + } + + .select-item:hover { + background-color: var(--slate3); + } + + + .item-renderer { + span { + font-size: 12px; + color: var(--slate12) + } + } + + } + } +} + +.sso-form-wrap { + .form-label { + font-size: 12px; + font-weight: 500px; + margin-bottom: 4px !important; + color: var(--slate12); + } + + .form-check-label { + font-size: 12px; + font-size: 12px; + line-height: 20px; + color: var(--slate12); + } +} + +.allow-default-sso-helper-text { + white-space: pre-line; +} + +.password-disable-danger-wrap { + padding: 16px; + gap: 16px; + width: 574px; + height: 116px; + background: var(--tomato3); + border: 1px solid var(--tomato5); + border-radius: 6px; +} + +.sso-footer-save-btn { + width: 157px; + height: 40px; +} + +.sso-footer-cancel-btn { + + width: 85px; + height: 40px; +} + +.danger-text-login { + padding-left: 40px !important; +} + +.tick-icon { + width: 20px; + height: 20px; + background: var(--indigo9); + border-radius: 4px; +} + +.invite-user-drawer-wrap { + display: grid; + grid-template-rows: auto 1fr auto; + height: 100vh; +} + +.manage-users-drawer-footer { + padding: 24px 32px; + height: 88px; + border-top: 1px solid var(--slate5) !important; + display: flex; + gap: 8px; + justify-content: end; + + .invite-btn { + width: 140px; + height: 40px; + } + + .cancel-btn { + width: 85px; + height: 40px; + } +} + + +.tj-drawer-tabs-wrap { + display: flex; +} + +.invite-user-drawer-wrap { + .card-header { + flex-direction: column; + display: flex; + justify-content: space-between; + padding: 0px !important; + } + + .card-header-inner-wrap { + justify-content: space-between; + width: 100%; + padding: 16px 20px; + height: 64px; + + } + + .card-header-inner-wrap, + .tj-drawer-tabs-container { + display: flex; + } + + .tj-drawer-tabs-container-outer { + padding-top: 0px; + gap: 10px; + height: 68px; + } + + .tj-drawer-tabs-container { + padding: 2px; + gap: 2px; + + width: 502px; + height: 36px; + background: var(--slate4); + border-radius: 6px; + + } +} + +.tj-drawer-tabs-btn { + padding: 2px 4px; + gap: 6px; + width: 248px; + height: 32px; + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-radius: 4px; + border: none; + color: var(--slate11); + display: flex; + align-items: center; + justify-content: center; + background: var(--slate4); + + + span { + margin-left: 4px !important; + font-weight: 500; + + } +} + +.tj-drawer-tabs-btn-active { + background: var(--base); + color: var(--slate12); +} + +.user-number-wrap { + display: flex; + flex-direction: column; + align-items: center; + padding: 8px; + gap: 10px; + width: 36px; + height: 36px; + background: var(--slate3); + border-radius: 1000px; +} + +.user-csv-template-wrap { + display: flex; + padding: 24px; + gap: 14px; + + width: 486px; + height: 152px; + + background: var(--orange3); + + border: 1px solid var(--orange6); + border-radius: 6px; + + div { + display: flex; + flex-direction: column; + + p { + margin-bottom: 12px; + } + + } +} + +.upload-user-form { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 60px 0px; + gap: 36px; + width: 486px; + height: 244px; + border: 2px dashed var(--indigo9); + border-radius: 6px; + align-items: center; + margin: 24px auto; + text-align: center; + + .select-csv-text { + color: var(--indigo9); + margin-bottom: 4px; + } + + span { + color: var(--slate11) !important; + } +} + +.download-template-btn { + width: 184px; + height: 32px; + padding: 0px !important; +} + +.csv-upload-icon-wrap { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 10px; + gap: 10px; + width: 64px; + height: 64px; + background: var(--indigo3); + border-radius: 12px; + margin: 0px auto 12px auto; + cursor: pointer; +} + +.user-csv-template-wrap { + margin-top: 24px; +} + + +.manage-users-drawer-content-bulk { + form { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + .manage-users-drawer-content-bulk-download-prompt { + display: flex; + flex-direction: row !important; + justify-content: center; + align-items: flex-start !important; + } +} + + +.manage-users-drawer-content { + margin: 24px 32px; + + div:first-child { + display: flex; + flex-direction: column; + justify-content: center; + align-items: top; + } + + .invite-user-by-email { + display: flex; + } + + .invite-email-body { + width: 452px; + + input { + padding: 6px 10px; + width: 470px; + height: 32px; + color: var(--slate12); + } + } +} + .tj-db-table { overflow-y: auto; height: 110px; - thead th { - position: sticky; - top: 0; - } - table { border-collapse: collapse; width: 100%; @@ -7689,10 +9530,620 @@ tbody { } +.sso-type-header { + margin-left: 10px; +} + +.groups-folder-list { + padding: 6px 8px; + gap: 40px; + max-width: 188px; + height: 32px; + + span { + white-space: nowrap !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + } +} + +.create-group-modal-footer { + display: flex; + align-items: center; + gap: 8px; + justify-content: end; +} + +.add-users-button { + width: 160px; + height: 32px; +} + +.sso-page-inputs { + padding: 6px 10px; + gap: 17px; + width: 612px; + height: 32px; +} + +.workspace-settings-filter-wrap { + background: var(--slate3); + padding: 15px 16px; + gap: 12px; + width: 880px; + height: 62px; + border-right: 1px solid var(--slate7); + border-top: 1px solid var(--slate7); + border-left: 1px solid var(--slate7); + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-top-left-radius: 6px; + border-top-right-radius: 6px; +} + + +// users page +.css-1i2tit0-menu { + margin: 0px !important; + background: var(--base); + box-shadow: 0px 4px 6px -2px #10182808 !important; + + .css-2kg7t4-MenuList { + margin: 0px !important; + padding: 0px !important; + background: var(--base); + } +} + +.workspace-settings-nav-items { + padding: 6px 8px; + gap: 40px; + width: 248px; + height: 32px; +} + +.new-app-dropdown { + background: var(--base) !important; + color: var(--slate12); +} + +.workspace-variable-container-wrap { + + .card, + thead { + background: var(--base) !important; + + tr>th, + tbody>tr>td { + background: var(--base) !important; + } + } + +} + +.move-selected-app-to-text { + p { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + span { + font-weight: 600; + } + } +} + +.tj-org-dropdown { + .dashboard-org-avatar { + margin-right: 11px; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 7px 8px; + gap: 10px; + width: 34px; + height: 34px; + background: var(--slate4) !important; + color: var(--slate9); + border-radius: 6px; + } + + .org-name { + color: var(--slate12) !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +} + +.css-1q0xftk-menu { + background-color: var(--base-black) !important; + border: 1px solid hsl(197, 6.8%, 13.6%) !important; + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03) !important; + +} + +.css-4yo7x8-menu { + background-color: var(--base) !important; + border: 1px solid var(--slate3) !important; + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03) !important; + border-radius: 6px !important; +} + + +.org-custom-select-header-wrap { + border-bottom: 1px solid var(--slate5); +} + +.btn-close:focus { + box-shadow: none !important; +} + +.template-card { + padding: 16px; + gap: 16px; + width: 272px; + height: 184px; + background: var(--base); + border: 1px solid var(--slate3); + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-radius: 6px; +} + +.see-all-temlplates-link { + color: var(--indigo9) !important; +} + +.template-card-img { + padding: 0px; + width: 240px; + height: 112px; + border-radius: 4px; +} + +.confirm-dialogue-body { + background: var(--base); + color: var(--slate12); +} + +.folder-header-icons-wrap { + gap: 4px; +} + +.tj-common-search-input { + .input-icon-addon { + padding-right: 8px; + padding-left: 8px; + + } + + input { + box-sizing: border-box; + display: flex; + flex-direction: row; + align-items: center; + padding: 4px 8px !important; + gap: 16px; + width: 248px !important; + height: 28px !important; + background: var(--base); + border: 1px solid var(--slate7); + border-radius: 6px; + color: var(--slate12); + padding-left: 33px !important; + + + ::placeholder { + color: var(--slate9); + margin-left: 5px !important; + padding-left: 5px !important; + background-color: red !important; + } + + &:hover { + background: var(--slate2); + border: 1px solid var(--slate8); + } + + &:active { + background: var(--indigo2); + border: 1px solid var(--indigo9); + box-shadow: 0px 0px 0px 2px #C6D4F9; + outline: none; + } + + &:focus-visible { + background: var(--slate2); + border: 1px solid var(--slate8); + border-radius: 6px; + outline: none; + } + + &:disabled { + background: var(--slate3); + border: 1px solid var(--slate7); + } + } + + +} + +.search-icon-wrap { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 7px; + gap: 8px; + width: 28px; + height: 28px; + background: var(--base); + border: 1px solid var(--slate7); + border-radius: 6px; + cursor: pointer; +} + +.sidebar-list-wrap { + margin-top: 24px; + padding: 0px 20px 20px 20px; + height: calc(100vh - 180px); + overflow: auto; + + span { + letter-spacing: -0.02em; + } +} + +.drawer-footer-btn-wrap, +.variable-form-footer { + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + padding: 24px 32px; + gap: 8px; + height: 88px; + border-top: 1px solid var(--slate5); + background: var(--base); +} + +.drawer-card-title { + padding: 16px; + border-bottom: 1px solid var(--slate5); + + h3 { + margin-bottom: 0px !important; + } +} + +.drawer-card-wrapper, +.variable-form-wrap { + min-height: 100vh; + display: grid; + grid-template-rows: auto 1fr auto; +} + +.add-new-datasource-header-container{ + margin-bottom: 24px; + padding-top: 4px; +} + +.folder-list-group-item { + color: var(--slate12) !important; +} + +.table-list-item, +.table-name { + color: var(--slate12) !important; +} + +// targetting all react select dropdowns + +.css-1i2tit0-menu .css-2kg7t4-MenuList { + div { + background-color: var(--base-black); + + &:hover { + background-color: hsl(198, 6.6%, 15.8%); + ; + } + } +} + +.css-ob45yj-menu .css-2kg7t4-MenuList { + div { + background-color: var(--base); + + &:hover { + background-color: var(--slate4); + ; + } + } +} + .selected-ds.row>img { padding: 0 !important; } +.tj-user-table-wrapper { + height: calc(100vh - 270px); //52+64+40+32+20+62 + overflow-y: auto; + background: var(--base); + border-right: 1px solid var(--slate7); + border-bottom: 1px solid var(--slate7); + border-left: 1px solid var(--slate7); + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + +} + +.user-filter-search { + padding: 6px 10px; + gap: 16px; + width: 312px; + height: 32px; + background: var(--base); + border: 1px solid var(--slate7); + border-radius: 6px; + + &::placeholder { + color: var(--slate9); + } +} + + + +//TJ APP INPUT +.tj-app-input { + display: flex; + flex-direction: column; + font-family: 'IBM Plex Sans'; + font-style: normal; + + .text-danger { + font-weight: 400 !important; + font-size: 10px !important; + line-height: 16px !important; + color: var(--tomato10) !important; + } + + label { + font-family: 'IBM Plex Sans'; + font-style: normal; + font-weight: 500; + font-size: 12px; + line-height: 20px; + display: flex; + align-items: center; + color: var(--slate12); + margin-bottom: 4px; + } + + input, + textarea, + .form-control { + gap: 16px !important; + background: var(--base) !important; + border: 1px solid var(--slate7) !important; + border-radius: 6px !important; + margin-bottom: 4px !important; + color: var(--slate12) !important; + + &:hover { + background: var(--slate1) !important; + border: 1px solid var(--slate8) !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + outline: none; + } + + &:focus-visible { + background: var(--indigo2) !important; + border: 1px solid var(--indigo9) !important; + box-shadow: none !important; + } + + } + +} + + + +.tj-sub-helper-text { + font-weight: 400; + font-size: 10px; + line-height: 16px; +} + +.tj-input-success { + color: var(--grass10); +} + +.tj-input-warning { + color: var(--orange10); +} + +.tj-input-helper { + color: var(--slate11); +} + +.tj-input-error { + color: var(--tomato10); +} + +.tj-input-error-state { + border: 1px solid var(--tomato9); +} + +// TJ APP INPUT END + +.search-input-container { + display: flex; +} + +// sidebar styles inside editor :: temporary +.theme-dark, +.dark-theme { + .editor { + .left-sidebar { + background-color: #232e3c !important; + } + } +} + +.tj-db-table { + table { + max-width: calc(100% - 28px); + } + + .datatable { + position: relative; + } +} + +.add-row-btn-database { + position: absolute; + top: 0; + right: -28px; + width: 28px; + height: 28px; + background: var(--slate7); + border-width: 0px 1px 1px 1px; + border-style: solid; + border-color: var(--slate4); + border-radius: 0px !important; +} + +.add-col-btn-database { + position: absolute; + top: 28; + left: 0px; + width: 28px; + height: 28px; + background: var(--slate7); + border-width: 0px 1px 1px 1px; + border-style: solid; + border-color: var(--slate4); + border-radius: 0px !important; +} + +// custom styles for users multiselect in manage users +.manage-groups-users-multiselect { + gap: 17px; + width: 440px; + height: 32px; + background: var(--base); + border-radius: 6px; + + .dropdown-heading { + height: 32px; + padding: 6px 10px; + } + + .dropdown-container { + background: var(--base); + border: 1px solid var(--slate7) !important; + } + + .dropdown-content { + border: 1px solid var(--slate3); + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + border-radius: 6px; + + .search { + input { + background-color: var(--base); + color: var(--slate12); + } + } + } + + .rmsc, + .dropdown-content, + .panel-content, + .search { + background: var(--base) !important; + } + + .options { + .select-item { + color: var(--slate12); + + &:hover { + background: var(--slate4); + border-radius: 6px; + } + } + } +} + +.select-search__options { + .item-renderer { + display: flex !important; + justify-content: space-between; + padding: 20px; + cursor: pointer; + flex-direction: row; + + div:first-child { + display: flex; + } + + p { + margin-bottom: 0px !important; + color: var(--slate12); + } + + span { + color: var(--slate11); + } + + p, + span { + font-weight: 400; + font-size: 12px; + line-height: 20px; + } + } +} + +.create-new-app-dropdown { + .button:first-child { + padding: 0 !important; + } + + .dropdown-toggle::after { + border: none !important; + content: url("data:image/svg+xml,%3Csvg width='25' height='25' viewBox='0 0 25 25' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.5 7.03906C10.5 6.34871 11.0596 5.78906 11.75 5.78906C12.4404 5.78906 13 6.34871 13 7.03906C13 7.72942 12.4404 8.28906 11.75 8.28906C11.0596 8.28906 10.5 7.72942 10.5 7.03906ZM10.5 12.0391C10.5 11.3487 11.0596 10.7891 11.75 10.7891C12.4404 10.7891 13 11.3487 13 12.0391C13 12.7294 12.4404 13.2891 11.75 13.2891C11.0596 13.2891 10.5 12.7294 10.5 12.0391ZM11.75 15.7891C11.0596 15.7891 10.5 16.3487 10.5 17.0391C10.5 17.7294 11.0596 18.2891 11.75 18.2891C12.4404 18.2891 13 17.7294 13 17.0391C13 16.3487 12.4404 15.7891 11.75 15.7891Z' fill='%23fff'/%3E%3C/svg%3E%0A"); + transform: rotate(360deg); + width: 14px; + margin: 0 !important; + display: flex; + align-items: center; + justify-content: center; + padding: 8px 0px 0px 0px; + } +} + +.sso-page-loader-card { + background-color: var(--slate2) !important; + height: 100%; + + .card-header { + background-color: var(--slate2) !important; + } +} + +.workspace-nav-list-wrap { + padding: 4px 20px 20px 20px; + height: calc(100vh - 116px) !important; +} + +.upload-user-form span.file-upload-error { + color: var(--tomato10) !important; +} + .tj-onboarding-phone-input { width: 392px !important; height: 40px; @@ -7747,4 +10198,189 @@ tbody { .react-tel-input .country-list .country.highlight { color: #6b6b6b; } +} + +.profile-page-content-wrap { + background-color: var(--slate2); + padding-top: 40px; +} + +.profile-page-card { + background-color: var(--base); + border-radius: 6px; +} + +.all-apps-link-cotainer { + border-radius: 6px !important; +} + +.variables-table-wrapper { + tr { + border-width: 0px !important; + } +} + +.home-page-content-container { + max-width: 880px; +} + +@media only screen and (max-width: 1583px) and (min-width: 1312px) { + + .homepage-app-card-list-item { + max-width: 264px; + } + +} + +@media only screen and (min-width: 1728px) { + + .homepage-app-card-list-item { + max-width: 304px; + } + + .home-page-content-container { + max-width: 976px; + } + + .liner { + width: 976px; + } +} + +@media only screen and (max-width: 992px) { + .homepage-app-card-list-item-wrap { + display: flex; + justify-content: center; + margin-left: auto; + margin-right: auto; + width: 100%; + margin-top: 22px; + } + + .homepage-app-card-list-item { + max-width: 304px !important; + flex-basis: 100%; + } +} + +@media only screen and (min-width: 993px) and (max-width: 1311px) { + .home-page-content-container { + max-width: 568px; + } + + .homepage-app-card-list-item-wrap { + row-gap: 20px; + } + + .homepage-app-card-list-item { + max-width: 269px; + flex-basis: 100%; + } + + .liner { + width: 568px; + } +} + +.tj-docs-link { + color: var(--indigo9) !important; + text-decoration: none; + list-style: none; +} + +.datasource-copy-button { + width: 87px; + height: 32px; +} + +.datasource-edit-btn { + height: 27px; + margin-left: 12px; +} + +.datasource-edit-modal { + + .modal-content, + .modal-body, + .modal-header, + .modal-title, + .modal-body-content, + .modal-sidebar, + .card { + background-color: var(--base) !important; + color: var(--slate12) !important; + border-color: var(--slate5) !important; + } + + .datasource-modal-sidebar-footer { + .footer-text { + color: var(--slate12) !important; + } + } + + .form-control-plaintext { + color: var(--slate12) !important; + } + + .card { + &:hover { + background-color: var(--slate2) !important; + } + } +} + +.org-edit-icon { + width: 28px; + height: 28px; + border-radius: 6px; + display: flex; + justify-content: center; + align-items: center; + + svg { + height: 14px; + width: 14px; + } +} + +.marketplace-body { + height: calc(100vh - 64px) !important; + overflow-y: auto; + background: var(--slate2); +} + +.plugins-card { + background-color: var(--base); + border: 1px solid var(--slate3); + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-radius: 6px; +} + +.template-source-name { + color: var(--slate12) !important; +} +.marketplace-install{ + color: var(--indigo9); +} +.popover { + .popover-arrow { + display: none; + } +} + +.shareable-link{ + .input-group{ + .tj-app-input textarea{ + width: 600px; + border-radius: 0px !important; + margin-bottom: 0px !important; + background-color: #5e656e !important; + color: #f4f6fa !important; + border: none !important; + + } + } +} +.confirm-dialogue-modal{ + background: var(--base); } \ No newline at end of file diff --git a/frontend/src/_styles/typography.scss b/frontend/src/_styles/typography.scss index f93a559530..550146767f 100644 --- a/frontend/src/_styles/typography.scss +++ b/frontend/src/_styles/typography.scss @@ -2,7 +2,7 @@ .tj-text { font-family: 'IBM Plex Sans'; font-style: normal; - color: #121212; + color: var(--slate12) !important; margin: 0; padding: 0; } @@ -87,7 +87,7 @@ .tj-text-md { font-weight: 400; - font-size: 14px; + font-size: 16px; line-height: 24px; margin: 0; padding: 0; @@ -115,13 +115,4 @@ line-height: 16px; margin: 0; padding: 0; -} - - -.tj-para-md { - font-weight: 500; - font-size: 16px; - line-height: 24px; - margin: 0; - padding: 0; } \ No newline at end of file diff --git a/frontend/src/_styles/ui-operations.scss b/frontend/src/_styles/ui-operations.scss new file mode 100644 index 0000000000..604835bbc6 --- /dev/null +++ b/frontend/src/_styles/ui-operations.scss @@ -0,0 +1,38 @@ +.ghost-black-operation { + color: var(--slate12); + border: none; + background: transparent; + + &:hover { + color: var(--slate11); + } + + &:active { + color: var(--indigo9); + } + + &:focus-visible { + color: var(--slate11); + background: var(--base); + border: none; + box-shadow: 0px 0px 0px 4px var(--slate6); + outline: none; + } + + &.applied { + background: var(--grass12); + border-radius: 6px; + } + + &.open { + background: var(--indigo4); + border: 1px solid var(--indigo9); + border-radius: 6px; + color: var(--indigo9) !important; + } + + &.disabled { + background: var(--slate3); + border-radius: 6px; + } +} \ No newline at end of file diff --git a/frontend/src/_styles/widgets/multi-select.scss b/frontend/src/_styles/widgets/multi-select.scss index 374d8f7f50..a745bd543f 100644 --- a/frontend/src/_styles/widgets/multi-select.scss +++ b/frontend/src/_styles/widgets/multi-select.scss @@ -1,149 +1,229 @@ @import "../colors.scss"; .tj-ms { - position: relative; - transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - border: 1px solid #dadcde; - border-radius: 4px; + position: relative; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + padding: 6px 10px; + gap: 17px; + width: 440px; + height: 32px; + background: var(--base) !important; + border: 1px solid var(--slate7); + border-radius: 6px !important; + display: flex; + align-items: center; - .select-search__select, .select-search-dark__select{ - display: none; - } - - .has-focus>.select-search__select, - .has-focus>.select-search-dark__select { - display: block; + + .select-search__select { + top: 34px; + } + + .select-search__select, + .select-search-dark { + background-color: var(--base) !important; + box-shadow: 0px 32px 64px -12px rgba(16, 24, 40, 0.14); + + &:focus-visible { + background: #F8FAFF; + border: 1px solid var(--indigo9); } + } - .select-search-dark.is-loading .select-search-dark__value::after { - background-size: 10px; - } - - .select-search-dark__value::after { - right: 13px; - width: 10px; - height: 10px; - } + .select-search__value { + background: var(--base); + } - .select-search-dark:not(.is-loading) .select-search-dark__value::after { - right: 13px; - width: 6px; - height: 6px; - } + .select-search__row { + border-top: none !important; + } - .select-search.is-loading .select-search__value::after { - background-size: 10px; - } + .select-search__option, + .select-search__not-found { + background-color: var(--base) !important; + color: var(--slate12); + border: 1px solid var(--slate5); + box-shadow: 0px 32px 64px -12px rgba(16, 24, 40, 0.14); + border-radius: 6px; + margin: 0 auto; + } - .select-search__value::after { - top: calc(50% - 4px); - right: 8px; - width: 10px; - height: 10px; - } - .select-search:not(.is-loading) .select-search__value::after { - right: 13px; - width: 6px; - height: 6px; - } + .select-search__input { + padding: 0 !important; + color: var(--slate9) !important; + background: var(--base) !important; + } - &:hover { - border-color: rgba(66, 153, 225, 0.1) - } - - .tj-ms-preview { - padding: 6px 3px; + .select-search__select, + .select-search-dark__select { + display: none; + } - .count-main { - padding: 1px 3px 1px 7px; - border: 1px solid #eaeaea; - border-radius: 5px; - background-color: #f0f2f6; - - .selected-count { - padding-right: 5px; - font-size: .8rem; - line-height: 20px; - white-space: nowrap; - } - - .select-close-btn { - color: #9a9191; - cursor: pointer; - } - } - - .count-main:hover { - border: 1px solid #d1cccc; - } - - svg { - width: 1rem; - } - } - - .select-search, - .select-search-dark { - position: unset; - - input { - border: none; - text-overflow: ellipsis; + .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; + } + + .select-search-dark__value::after { + right: 13px; + width: 10px; + height: 10px; + } + + .select-search-dark:not(.is-loading) .select-search-dark__value::after { + right: 13px; + width: 6px; + height: 6px; + } + + .select-search.is-loading .select-search__value::after { + background-size: 10px; + } + + .select-search__value::after { + top: calc(50% - 4px); + right: 8px; + width: 10px; + height: 10px; + } + + .select-search:not(.is-loading) .select-search__value::after { + right: 13px; + width: 6px; + height: 6px; + } + + &:hover { + border-color: rgba(66, 153, 225, 0.1) + } + + .tj-ms-preview { + padding: 3px 3px; + + .count-main { + padding: 1px 3px 1px 7px; + border: 1px solid var(--slate5); + border-radius: 5px; + background-color: var(--slate8); + color: var(--base); + + .selected-count { + padding-right: 5px; + font-size: .8rem; + line-height: 20px; white-space: nowrap; - overflow: hidden; + } + + .select-close-btn { + color: #9a9191; + cursor: pointer; + } + } + + .count-main:hover { + border: 1px solid #d1cccc; + } + + svg { + width: 1rem; + } + } + + .select-search, + .select-search-dark { + position: unset; + + input { + border: none; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + } + + .select-search-dark__input { + background-color: var(--base); + border: none; + } + + .select-search-dark__select { + background-color: var(--base); + padding: 14px; + border: 1px solid var(--slate5); + box-shadow: 0px 32px 64px -12px rgba(16, 24, 40, 0.14); + border-radius: 6px; + top: 34px !important; + + ul { + width: 100%; + + + li { + .item-renderer { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + height: 60px; + + + div { + display: flex; + } + } + } } } - - .tj-ms-count { - border-radius: 2px; +} + +.tj-ms-count { + border-radius: 2px; + display: flex; + + .tj-ms-preview { + z-index: 2; display: flex; - - .tj-ms-preview { - z-index: 2; - padding: 6px; - } + align-items: center; } - - .theme-dark .tj-ms { - background-color: #273342; - border: 1px solid #232e3c; - - &:hover { - border-color: $white; - } - - .tj-ms-count { - color: $white; - } - - .tj-ms-preview { - color: #273342; - } +} + +.tj-ms-count { + &:hover { + border: 1px solid var(--indigo9) !important; } - - .selected-section { - overflow: auto; - - .tj-ms { - border: none; - background-color: unset; - float: left; - } - - .selected-heading { - padding: 1px 7px 1px 7px; - margin: 6px 2px 6px 5px; - border-radius: 6px; - font-weight: 500; - float: left; - } - - .selected-text { - line-height: 24px; - font-weight: 300; - margin: 6px 0px; - white-space: nowrap; - float: left; - } - } \ No newline at end of file + + &:focus-visible { + border: 1px solid var(--indigo9); + } +} + +.selected-section { + overflow: auto; + + .tj-ms { + border: none; + background-color: unset; + float: left; + } + + .selected-heading { + padding: 1px 7px 1px 7px; + margin: 6px 2px 6px 5px; + border-radius: 6px; + font-weight: 500; + float: left; + } + + .selected-text { + line-height: 24px; + font-weight: 300; + margin: 6px 0px; + white-space: nowrap; + float: left; + } +} \ No newline at end of file diff --git a/frontend/src/_ui/AlertDialog/index.jsx b/frontend/src/_ui/AlertDialog/index.jsx index c910d1c3c3..ec73e79a1d 100644 --- a/frontend/src/_ui/AlertDialog/index.jsx +++ b/frontend/src/_ui/AlertDialog/index.jsx @@ -2,12 +2,29 @@ import React from 'react'; import Modal from 'react-bootstrap/Modal'; import cx from 'classnames'; -export default function AlertDialog({ title, size = 'sm', show, closeModal, customClassName, children }) { +export default function AlertDialog({ + title, + size = 'sm', + show, + closeModal, + customClassName, + children, + checkForBackground = false, +}) { const darkMode = localStorage.getItem('darkMode') === 'true'; + //checkForBackground :: remove this once all ui is revamped used only so that editor styles is unaltered return ( closeModal(false)} - contentClassName={cx('animation-fade home-modal-component', customClassName, { dark: darkMode })} + contentClassName={cx( + `animation-fade ${!checkForBackground ? 'home-modal-component' : 'home-modal-component-editor'} ${ + darkMode && 'dark-theme' + }`, + customClassName, + { + dark: checkForBackground && darkMode, + } + )} show={show} size={size} backdrop={'static'} @@ -18,7 +35,7 @@ export default function AlertDialog({ title, size = 'sm', show, closeModal, cust centered data-cy={'modal-component'} style={{ zIndex: 9992 }} - backdropClassName="home-modal-backdrop" + // backdropClassName="home-modal-backdrop" > {title && ( diff --git a/frontend/src/_ui/AppButton/AppButton.jsx b/frontend/src/_ui/AppButton/AppButton.jsx new file mode 100644 index 0000000000..f97938397d --- /dev/null +++ b/frontend/src/_ui/AppButton/AppButton.jsx @@ -0,0 +1,78 @@ +import React from 'react'; +import './AppButton.scss'; +import SolidIcon from '../Icon/solidIcons/index'; +import { Spinner } from 'react-bootstrap'; + +export const ButtonBase = function ButtonBase(props) { + const mapBaseSize = { + lg: 'tj-large-btn', + md: 'tj-medium-btn', + sm: 'tj-small-btn', + xs: 'tj-extra-small-btn', + }; + + const { + className, + size = 'lg', // specify size otherwise large button dimesnions will be applied with min width + as = 'button', // render it as a button or an anchor. + children, + disabled, + leftIcon, + rightIcon, + backgroundColor, + type, + isLoading, + fill, + iconCustomClass, + iconWidth, + ...restProps + } = props; + + const isAnchor = (!!restProps.href || as === 'a') && !disabled; + const Element = as ? as : isAnchor ? 'a' : 'button'; + + return ( + + {!isLoading && leftIcon && ( + + {} + + )} + {isLoading ? ( +
          + +
          + ) : ( + children + )} + {!isLoading && rightIcon && ( + + {} + + )} +
          + ); +}; + +export const ButtonSolid = function ButtonSolid(props) { + const mapVariant = { + primary: 'tj-primary-btn', + ghostBlue: 'tj-ghost-blue-btn', + ghostBlack: 'tj-ghost-black-btn', + secondary: 'tj-secondary-btn', + tertiary: 'tj-tertiary-btn', + dangerPrimary: 'tj-primary-danger-btn', + dangerSecondary: 'tj-secondary-danger-btn', + dangerTertiary: 'tj-tertiary-danger-btn', + dangerGhost: 'tj-ghost-danger-btn', + }; + + const { variant = 'primary', className, ...restProps } = props; + return ; +}; diff --git a/frontend/src/_ui/AppButton/AppButton.scss b/frontend/src/_ui/AppButton/AppButton.scss new file mode 100644 index 0000000000..5903d7fc00 --- /dev/null +++ b/frontend/src/_ui/AppButton/AppButton.scss @@ -0,0 +1,344 @@ +@import "../../_styles/designtheme.scss"; + +.tj-base-btn { + box-sizing: border-box; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 10px 20px; + gap: 8px; + border-radius: 6px; + font-weight: 600; + text-decoration: none; + cursor: pointer; + line-height: 20px; + text-decoration: none !important; + + .tj-btn-left-icon, + .tj-btn-right-icon { + display: flex; + align-items: center; + } + + &:disabled { + background: var(--slate3) !important; + color: var(--slate8) !important; + } +} + +.tj-large-btn { + height: 40px; + border-radius: 6px; + padding: 10px 20px 10px 20px; + font-size: 14px; +} + +.tj-medium-btn { + height: 32px; + border-radius: 6px; + padding: 6px 16px 6px 16px; + font-size: 14px; +} + +.tj-small-btn { + height: 28px; + border-radius: 6px; + padding: 4px 16px 4px 16px; + font-size: 12px; +} + +.tj-extra-small-btn { + height: 20px; + border-radius: 4px; + padding: 0px 8px 0px 8px; + font-size: 12px; +} + +/* Order */ +.tj-reverse-btn { + flex-direction: row-reverse; +} + +/* Variants */ +.tj-primary-btn { + background: #3E63DD; + border: none; + color: #FDFDFE; + + &:hover { + background: #3A5CCC; + color: #FDFDFE; + + } + + &:focus-visible { + box-shadow: 0px 0px 0px 4px var(--indigo6); + background: #3A5CCC; + outline: none; + color: #FDFDFE; + + } + + + &:active { + background: #3451B2; + box-shadow: none; + color: #F0F4FF; + } + +} + +.tj-secondary-btn { + background: var(--indigo3); + border: none; + color: var(--indigo11); + + &:hover { + background: var(--indigo4); + color: var(--indigo10); + } + + &:active { + background: var(--indigo5); + color: var(--indigo8); + box-shadow: none; + } + + &:focus-visible { + background: #E6EDFE; + box-shadow: 0px 0px 0px 4px var(--indigo6); + color: var(--indigo10); + outline: none; + } +} + +.tj-tertiary-btn { + background: var(--base); + border: 1px solid var(--slate7); + color: var(--slate12); + + &:hover { + background: var(--slate8); + color: var(--slate11); + border: 1px solid var(--slate8); + background: var(--base); + } + + &:active { + background: var(--base); + box-shadow: none; + border: 1px solid var(--slate12); + color: var(--slate12); + } + + &:focus-visible { + background: var(--base); + color: var(--slate11); + outline: 1px solid var(--slate8); + box-shadow: 0px 0px 0px 4px var(--slate6); + outline: none; + } +} + + +.tj-ghost-blue-btn { + color: var(--indigo9); + border: none; + background: transparent; + + &:hover { + color: var(--indigo10); + } + + &:active { + color: var(--indigo8); + } + + &:focus-visible { + color: var(--indigo10); + background: var(--base); + border: none; + box-shadow: 0px 0px 0px 4px var(--indigo6); + outline: none; + } +} + +.tj-ghost-black-btn { + color: var(--slate12); + border: none; + background: transparent; + + &:hover { + color: var(--slate11); + } + + &:active { + color: var(--slate12); + } + + &:focus-visible { + color: var(--slate11); + background: var(--base); + border: none; + box-shadow: 0px 0px 0px 4px var(--slate6); + outline: none; + } +} + +/* // Danger-variants--- */ + +.tj-primary-danger-btn { + background: #E54D2E; + border: none; + color: #FFFCFC; + + &:hover { + background: var(--tomato10); + } + + &:active { + background: var(--tomato11); + color: #F0F4FF + } + + &:focus-visible { + background: var(--tomato10); + border: none; + box-shadow: 0px 0px 0px 4px var(--tomato6); + outline: none; + } + +} + +.tj-secondary-danger-btn { + background: var(--tomato3); + border: none; + color: var(--tomato9); + + &:hover { + background: var(--tomato4); + color: var(--tomato10); + } + + &:active { + background: var(--tomato5); + color: var(--tomato8); + } + + &:focus-visible { + background: var(--tomato4); + border: none; + box-shadow: 0px 0px 0px 4px var(--tomato6); + color: var(--tomato8); + outline: none; + } +} + +.tj-tertiary-danger-btn { + background: var(--base); + border: 1px solid var(--tomato7); + color: var(--tomato9); + + &:hover { + background: var(--base); + color: var(--tomato10); + border: 1px solid var(--tomato5); + } + + &:active { + background: var(--base); + color: var(--tomato11); + border: 1px solid var(--tomato11); + } + + &:focus-visible { + background: var(--base); + outline: 1px solid var(--tomato5); + box-shadow: 0px 0px 0px 4px var(--tomato6); + color: var(--tomato10); + outline: none; + } + +} + + +.tj-ghost-danger-btn { + color: var(--tomato9); + background: transparent; + border: none; + + &:hover { + color: var(--tomato10); + } + + &:active { + color: var(--tomato8); + } + + &:focus-visible { + border: none; + box-shadow: 0px 0px 0px 4px var(--tomato6); + outline: none; + } +} + +.tj-disabled-btn { + background: var(--slate3) !important; + color: var(--slate8) !important; + border: none !important; +} + +.dark-theme { + tj-primary-btn { + background: #3E63DD; + border: none; + color: #FDFDFE; + + &:hover { + background: var(--indigo10); + border: 1px solid #FFFFFF + } + + &:focus-visible { + box-shadow: 0px 0px 0px 4px var(--indigo6); + border: 1px solid #FFFFFF; + outline: none; + } + + + &:active { + background: #849DFF; + box-shadow: none; + } + + } + + + /* // Danger-variants--- */ + + .tj-primary-danger-btn { + background: var(--tomato9); + border: none; + color: #FFFCFC; + + &:hover { + background: var(--tomato10); + color: #FFFCFC; + } + + &:active { + background: var(--tomato11); + color: #F0F4FF; + } + + &:focus-visible { + background: var(--tomato10); + border: none; + box-shadow: 0px 0px 0px 4px var(--tomato6); + outline: none; + color: #FFFCFC; + } + + } +} \ No newline at end of file diff --git a/frontend/src/_ui/AppInput/AppInput.jsx b/frontend/src/_ui/AppInput/AppInput.jsx new file mode 100644 index 0000000000..0f0712a77d --- /dev/null +++ b/frontend/src/_ui/AppInput/AppInput.jsx @@ -0,0 +1,72 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import './AppInput.scss'; + +const InputField = ({ + value, + label, + placeholder = '', + errorMessage, + type = 'input', + onChange, + disabled, + className = '', + currentState = 'none', +}) => { + const [data, setData] = useState(value); + const mapHelpers = { + success: 'tj-input-success', + warning: 'tj-input-warning', + error: 'tj-input-error', + helper: 'tj-input-helper', + }; + + const handleChange = (event) => { + const { value } = event.target; + onChange(value); + setData(data); + }; + + return ( +
          + {label && } + + {type === 'textarea' ? ( +