mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-18 14:38:32 +00:00
Merge pull request #10114 from ToolJet/release/platformv18
Release Platform v18
This commit is contained in:
commit
0ca40bc52e
101 changed files with 44651 additions and 7253 deletions
|
|
@ -296,7 +296,6 @@ describe(
|
|||
});
|
||||
});
|
||||
|
||||
cy.get(usersSelector.acceptInvite).click();
|
||||
cy.get('[data-cy="draggable-widget-table1"]').should("be.visible");
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ describe("Password reset functionality", () => {
|
|||
);
|
||||
cy.get(commonSelectors.forgotPasswordPageSubHeader).should(($el) => {
|
||||
expect($el.contents().first().text().trim()).to.eq(
|
||||
commonText.newToTooljetText
|
||||
"New to"
|
||||
);
|
||||
});
|
||||
cy.get(commonSelectors.createAnAccountLink).verifyVisibleElement(
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ describe("User signup", () => {
|
|||
it("Verify invalid invitation link", () => {
|
||||
cy.log(invitationLink)
|
||||
cy.visit(invitationLink);
|
||||
cy.pause()
|
||||
verifyInvalidInvitationLink();
|
||||
cy.get(commonSelectors.backtoSignUpButton).click();
|
||||
cy.get(commonSelectors.SignUpSectionHeader).should("be.visible");
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import { buttonText } from "Texts/button";
|
|||
|
||||
describe("App Export Functionality", () => {
|
||||
var data = {};
|
||||
data.appName1 = `${fake.companyName}-App`;
|
||||
let currentVersion = "";
|
||||
let otherVersions = [];
|
||||
beforeEach(() => {
|
||||
|
|
@ -29,6 +28,7 @@ describe("App Export Functionality", () => {
|
|||
});
|
||||
|
||||
it("Verify the elements of export dialog box", () => {
|
||||
data.appName1 = `${fake.companyName}-App`;
|
||||
cy.apiCreateApp(data.appName1);
|
||||
cy.openApp();
|
||||
cy.dragAndDropWidget(buttonText.defaultWidgetText);
|
||||
|
|
@ -56,6 +56,9 @@ describe("App Export Functionality", () => {
|
|||
});
|
||||
|
||||
it("Verify 'Export app' functionality of an application", () => {
|
||||
data.appName1 = `${fake.companyName}-App`;
|
||||
cy.apiCreateApp(data.appName1);
|
||||
|
||||
cy.visit("/");
|
||||
cy.get(commonSelectors.appHeaderLable).should("be.visible");
|
||||
|
||||
|
|
@ -96,7 +99,7 @@ describe("App Export Functionality", () => {
|
|||
.click();
|
||||
createNewVersion((otherVersions = ["v2"]), (currentVersion = "v1"));
|
||||
cy.wait(500);
|
||||
cy.dragAndDropWidget("Toggle Switch", 50, 50);
|
||||
cy.dragAndDropWidget("Text Input", 50, 50);
|
||||
cy.waitForAutoSave();
|
||||
cy.get(appVersionSelectors.currentVersionField((otherVersions = "v2")))
|
||||
.should("be.visible")
|
||||
|
|
@ -34,9 +34,9 @@ describe("Bulk user upload", () => {
|
|||
data.firstName = fake.firstName;
|
||||
data.workspaceName = data.firstName.toLowerCase();
|
||||
|
||||
cy.apiLogin()
|
||||
cy.apiLogin();
|
||||
cy.apiCreateWorkspace(data.firstName, data.workspaceName);
|
||||
cy.visit(`${data.workspaceName}`)
|
||||
cy.visit(`${data.workspaceName}`);
|
||||
|
||||
common.navigateToManageUsers();
|
||||
|
||||
|
|
@ -122,9 +122,9 @@ describe("Bulk user upload", () => {
|
|||
data.firstName = fake.firstName;
|
||||
data.workspaceName = data.firstName.toLowerCase();
|
||||
|
||||
cy.apiLogin()
|
||||
cy.apiLogin();
|
||||
cy.apiCreateWorkspace(data.firstName, data.workspaceName);
|
||||
cy.visit(`${data.workspaceName}`)
|
||||
cy.visit(`${data.workspaceName}`);
|
||||
common.navigateToManageUsers();
|
||||
|
||||
cy.get(usersSelector.buttonAddUsers).click();
|
||||
|
|
@ -142,7 +142,10 @@ describe("Bulk user upload", () => {
|
|||
force: true,
|
||||
});
|
||||
cy.get(usersSelector.buttonUploadUsers).click();
|
||||
cy.wait(10000);
|
||||
cy.wait(30000);
|
||||
cy.get(".go2072408551")
|
||||
.should("be.visible")
|
||||
.and("have.text", "250 users are being added");
|
||||
common.searchUser("test12@gmail.com");
|
||||
cy.contains("td", "test12@gmail.com")
|
||||
.parent()
|
||||
|
|
|
|||
|
|
@ -131,7 +131,8 @@ describe("user invite flow cases", () => {
|
|||
cy.apiLogin();
|
||||
cy.apiCreateWorkspace(data.workspaceName, data.workspaceName);
|
||||
cy.visit(`${data.workspaceName}`);
|
||||
enableSignUp();
|
||||
cy.wait(3000)
|
||||
setSignupStatus(true, data.workspaceName);
|
||||
logout();
|
||||
|
||||
cy.get(commonSelectors.createAnAccountLink).click();
|
||||
|
|
@ -142,11 +143,6 @@ describe("user invite flow cases", () => {
|
|||
|
||||
cy.defaultWorkspaceLogin();
|
||||
visitWorkspaceInvitation(data.email, data.workspaceName);
|
||||
|
||||
cy.clearAndType(commonSelectors.workEmailInputField, data.email);
|
||||
cy.clearAndType(commonSelectors.passwordInputField, "password");
|
||||
cy.get(commonSelectors.signInButton).click();
|
||||
cy.get(usersSelector.acceptInvite).click();
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.inviteToast);
|
||||
logout();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -552,21 +552,25 @@ export const defaultSSO = (enable) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const setSignupStatus = (enable) => {
|
||||
cy.getCookie("tj_auth_token").then((cookie) => {
|
||||
cy.request(
|
||||
{
|
||||
export const setSignupStatus = (enable, workspaceName = 'My workspace') => {
|
||||
cy.task("updateId", {
|
||||
dbconfig: Cypress.env("app_db"),
|
||||
sql: `SELECT id FROM organizations WHERE name = '${workspaceName}'`,
|
||||
}).then((resp) => {
|
||||
const workspaceId = resp.rows[0].id;
|
||||
|
||||
cy.getCookie("tj_auth_token").then((cookie) => {
|
||||
cy.request({
|
||||
method: "PATCH",
|
||||
url: "http://localhost:3000/api/organizations",
|
||||
headers: {
|
||||
"Tj-Workspace-Id": Cypress.env("workspaceId"),
|
||||
"Tj-Workspace-Id": workspaceId,
|
||||
Cookie: `tj_auth_token=${cookie.value}`,
|
||||
},
|
||||
body: { enableSignUp: enable },
|
||||
},
|
||||
{ log: false }
|
||||
).then((response) => {
|
||||
expect(response.status).to.equal(200);
|
||||
}).then((response) => {
|
||||
expect(response.status).to.equal(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
display: flex;
|
||||
padding: 1.5rem;
|
||||
flex: 1 1 auto;
|
||||
align-items: start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.card-info {
|
||||
|
|
|
|||
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
|
|
@ -21,3 +21,5 @@
|
|||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
storybook-static
|
||||
14
frontend/.storybook/decorators.jsx
Normal file
14
frontend/.storybook/decorators.jsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// storybookDecorators.js
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export function withColorScheme(story, context) {
|
||||
const darkMode = context?.globals?.backgrounds?.value === '#333333'; // Access theme mode from globals
|
||||
const className = darkMode ? 'dark-theme' : '';
|
||||
|
||||
return (
|
||||
<div className={className} style={{ backgroundColor: 'transparent' }}>
|
||||
{story()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
/** @type { import('@storybook/react-webpack5').StorybookConfig } */
|
||||
import custom from '../webpack.config'
|
||||
const path = require('path');
|
||||
import customWebpackConfig from '../webpack.config';
|
||||
import path from 'path';
|
||||
|
||||
const config = {
|
||||
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
|
|
@ -17,16 +16,19 @@ const config = {
|
|||
docs: {
|
||||
autodocs: "tag",
|
||||
},
|
||||
webpackFinal: async (config) => {
|
||||
webpackFinal: async (storybookConfig) => {
|
||||
return {
|
||||
...config,
|
||||
module: { ...config.module, rules: [...config.module.rules, ...custom.module.rules] },
|
||||
resolve : {
|
||||
alias : {
|
||||
'@': path.resolve(__dirname, 'src/')
|
||||
...storybookConfig,
|
||||
module: { ...storybookConfig.module, rules: [...storybookConfig.module.rules, ...customWebpackConfig.module.rules] },
|
||||
resolve: {
|
||||
...storybookConfig.resolve,
|
||||
alias: {
|
||||
...storybookConfig.resolve.alias,
|
||||
'@': path.resolve(__dirname, '../src/')
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
/** @type { import('@storybook/react').Preview } */
|
||||
// import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
|
||||
import '../src/_styles/theme.scss';
|
||||
import './preview.scss'
|
||||
import './preview.scss';
|
||||
import { withColorScheme } from './decorators'; // Import the decorator
|
||||
|
||||
const preview = {
|
||||
parameters: {
|
||||
|
|
@ -13,6 +14,7 @@ const preview = {
|
|||
},
|
||||
},
|
||||
},
|
||||
decorators: [withColorScheme], // Adding the decorator to the decorators array
|
||||
};
|
||||
|
||||
export default preview;
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
@import '~bootstrap/scss/bootstrap';
|
||||
|
||||
@import '../src/_styles/componentdesign.scss'
|
||||
131
frontend/ce/white-label/whiteLabelling.js
Normal file
131
frontend/ce/white-label/whiteLabelling.js
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import { decodeEntities } from '@/_helpers/utils';
|
||||
|
||||
export const defaultWhiteLabellingSettings = {
|
||||
WHITE_LABEL_LOGO: 'assets/images/rocket.svg',
|
||||
WHITE_LABEL_TEXT: 'ToolJet',
|
||||
WHITE_LABEL_FAVICON: 'assets/images/logo.svg',
|
||||
};
|
||||
|
||||
export const whiteLabellingOptions = {
|
||||
WHITE_LABEL_LOGO: 'App Logo',
|
||||
WHITE_LABEL_TEXT: 'Page Title',
|
||||
WHITE_LABEL_FAVICON: 'Favicon',
|
||||
};
|
||||
|
||||
export async function fetchWhiteLabelDetails() {}
|
||||
|
||||
export async function checkWhiteLabelsDefaultState() {
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function resetToDefaultWhiteLabels() {}
|
||||
|
||||
export function retrieveWhiteLabelText() {
|
||||
return window.public_config?.WHITE_LABEL_TEXT || defaultWhiteLabellingSettings.WHITE_LABEL_TEXT;
|
||||
}
|
||||
|
||||
export function retrieveWhiteLabelLogo() {
|
||||
return window.public_config?.WHITE_LABEL_LOGO || defaultWhiteLabellingSettings.WHITE_LABEL_LOGO;
|
||||
}
|
||||
|
||||
export function retrieveWhiteLabelFavicon() {
|
||||
return window.public_config?.WHITE_LABEL_FAVICON || defaultWhiteLabellingSettings.WHITE_LABEL_FAVICON;
|
||||
}
|
||||
|
||||
export const pageTitles = {
|
||||
INSTANCE_SETTINGS: 'Settings',
|
||||
WORKSPACE_SETTINGS: 'Workspace settings',
|
||||
INTEGRATIONS: 'Marketplace',
|
||||
WORKFLOWS: 'Workflows',
|
||||
DATABASE: 'Database',
|
||||
DATA_SOURCES: 'Data sources',
|
||||
AUDIT_LOGS: 'Audit logs',
|
||||
ACCOUNT_SETTINGS: 'Profile settings',
|
||||
SETTINGS: 'Profile settings',
|
||||
EDITOR: 'Editor',
|
||||
WORKFLOW_EDITOR: 'workflowEditor',
|
||||
VIEWER: 'Viewer',
|
||||
DASHBOARD: 'Dashboard',
|
||||
WORKSPACE_CONSTANTS: 'Workspace constants',
|
||||
};
|
||||
|
||||
// to set favicon and title from router for individual pages
|
||||
export async function setFaviconAndTitle(whiteLabelFavicon, whiteLabelText, location) {
|
||||
if (!whiteLabelFavicon || !whiteLabelText) {
|
||||
whiteLabelFavicon = await retrieveWhiteLabelFavicon();
|
||||
whiteLabelText = await retrieveWhiteLabelText();
|
||||
}
|
||||
// Set favicon
|
||||
let links = document.querySelectorAll("link[rel='icon']");
|
||||
if (links.length === 0) {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'icon';
|
||||
link.type = 'image/svg+xml';
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
links = [link];
|
||||
}
|
||||
links.forEach((link) => {
|
||||
link.href = `${whiteLabelFavicon || defaultWhiteLabellingSettings.WHITE_LABEL_FAVICON}`;
|
||||
});
|
||||
// Set title
|
||||
const isEditorOrViewerGoingToRender = ['/apps/', '/applications/'].some((path) => location?.pathname.includes(path));
|
||||
if (isEditorOrViewerGoingToRender) {
|
||||
return;
|
||||
}
|
||||
const pathToTitle = {
|
||||
'instance-settings': pageTitles.INSTANCE_SETTINGS,
|
||||
'workspace-settings': pageTitles.WORKSPACE_SETTINGS,
|
||||
integrations: pageTitles.INTEGRATIONS,
|
||||
workflows: pageTitles.WORKFLOWS,
|
||||
'data-sources': pageTitles.DATA_SOURCES,
|
||||
'audit-logs': pageTitles.AUDIT_LOGS,
|
||||
'account-settings': pageTitles.ACCOUNT_SETTINGS,
|
||||
settings: pageTitles.INSTANCE_SETTINGS,
|
||||
login: '',
|
||||
signUp: '',
|
||||
error: '',
|
||||
signup: '',
|
||||
'organization-invitations': '',
|
||||
invitation: '',
|
||||
'forgot-password': '',
|
||||
'reset-password': '',
|
||||
'workspace-constants': pageTitles.WORKSPACE_CONSTANTS,
|
||||
setup: '',
|
||||
};
|
||||
const pageTitleKey = Object.keys(pathToTitle).find((path) => location?.pathname.includes(path));
|
||||
const pageTitle = pathToTitle[pageTitleKey];
|
||||
|
||||
//For undefined routes
|
||||
if (pageTitle === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pageTitleKey && !isEditorOrViewerGoingToRender) {
|
||||
document.title = pageTitle
|
||||
? `${decodeEntities(pageTitle)} | ${whiteLabelText || defaultWhiteLabellingSettings.WHITE_LABEL_TEXT}`
|
||||
: `${decodeEntities(whiteLabelText) || defaultWhiteLabellingSettings.WHITE_LABEL_TEXT}`;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAndSetWindowTitle(pageDetails) {
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
let pageTitleKey = pageDetails?.page || '';
|
||||
let pageTitle = '';
|
||||
switch (pageTitleKey) {
|
||||
case pageTitles.VIEWER: {
|
||||
const titlePrefix = pageDetails?.preview ? 'Preview - ' : '';
|
||||
pageTitle = `${titlePrefix}${pageDetails?.appName || 'My App'}`;
|
||||
break;
|
||||
}
|
||||
case pageTitles.EDITOR:
|
||||
case pageTitles.WORKFLOW_EDITOR: {
|
||||
pageTitle = pageDetails?.appName || 'My App';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pageTitle = pageTitleKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
document.title = !(pageDetails?.preview === false) ? `${pageTitle} | ${whiteLabelText}` : `${pageTitle}`;
|
||||
}
|
||||
17
frontend/components.json
Normal file
17
frontend/components.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": false,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/styles/theme.scss",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
9
frontend/netlify.toml
Normal file
9
frontend/netlify.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[build]
|
||||
base = "frontend/"
|
||||
publish = "storybook-static"
|
||||
command = "npx storybook build"
|
||||
|
||||
[template.environment]
|
||||
NODE_ENV = "production"
|
||||
NODE_VERSION = "18.18.2"
|
||||
NPM_VERSION = "9.8.1"
|
||||
30517
frontend/package-lock.json
generated
30517
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -19,6 +19,7 @@
|
|||
"@radix-ui/colors": "^0.1.8",
|
||||
"@radix-ui/react-popover": "^1.0.3",
|
||||
"@radix-ui/react-slider": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-toggle-group": "^1.0.4",
|
||||
"@react-google-maps/api": "^2.18.1",
|
||||
"@sentry/react": "^7.100.1",
|
||||
|
|
@ -35,6 +36,7 @@
|
|||
"acorn": "^8.11.3",
|
||||
"axios": "^1.3.3",
|
||||
"bootstrap": "^5.2.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"classnames": "^2.3.2",
|
||||
"deep-object-diff": "^1.1.9",
|
||||
"dompurify": "^3.0.0",
|
||||
|
|
@ -110,6 +112,8 @@
|
|||
"semver": "^7.3.8",
|
||||
"string-hash": "^1.1.3",
|
||||
"superstruct": "^1.0.3",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"url-join": "^5.0.0",
|
||||
"use-react-router-breadcrumbs": "^4.0.1",
|
||||
|
|
@ -137,6 +141,7 @@
|
|||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"babel-loader": "^9.1.2",
|
||||
"babel-plugin-console-source": "^2.0.5",
|
||||
"babel-plugin-import": "^1.13.6",
|
||||
|
|
@ -158,10 +163,13 @@
|
|||
"jest": "^29.4.2",
|
||||
"node-sass": "^8.0.0",
|
||||
"path": "^0.12.7",
|
||||
"postcss": "^8.4.35",
|
||||
"postcss-loader": "^8.1.0",
|
||||
"prettier": "^2.8.4",
|
||||
"sass-loader": "^13.2.0",
|
||||
"storybook": "^7.2.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"terser-webpack-plugin": "^5.3.6",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.1",
|
||||
|
|
@ -192,7 +200,7 @@
|
|||
"format": "eslint . --fix '**/*.{js,jsx}'",
|
||||
"test": "jest",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
"build-storybook": "npx storybook build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
|
|
|
|||
8
frontend/postcss.config.js
Normal file
8
frontend/postcss.config.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// postcss.config.js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('tailwindcss'),
|
||||
require('autoprefixer'),
|
||||
// Other PostCSS plugins if needed
|
||||
],
|
||||
};
|
||||
|
|
@ -39,6 +39,7 @@ import { ManageOrgUsers } from '@/ManageOrgUsers';
|
|||
import { ManageGroupPermissions } from '@/ManageGroupPermissions';
|
||||
import OrganizationLogin from '@/_components/OrganizationLogin/OrganizationLogin';
|
||||
import { ManageOrgVars } from '@/ManageOrgVars';
|
||||
import { setFaviconAndTitle } from '@white-label/whiteLabelling';
|
||||
|
||||
const AppWrapper = (props) => {
|
||||
const { isAppDarkMode } = useAppDarkMode();
|
||||
|
|
@ -78,6 +79,7 @@ class AppComponent extends React.Component {
|
|||
componentDidMount() {
|
||||
authorizeWorkspace();
|
||||
this.fetchMetadata();
|
||||
setFaviconAndTitle(null, null, this.props.location);
|
||||
setInterval(this.fetchMetadata, 1000 * 60 * 60 * 1);
|
||||
}
|
||||
// check if its getting routed from editor
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ import Spinner from '@/_ui/Spinner';
|
|||
import { withRouter } from '@/_hoc/withRouter';
|
||||
import { onLoginSuccess } from '@/_helpers/platform/utils/auth.utils';
|
||||
import { updateCurrentSession } from '@/_helpers/authorizeWorkspace';
|
||||
import {
|
||||
retrieveWhiteLabelText,
|
||||
setFaviconAndTitle,
|
||||
retrieveWhiteLabelFavicon,
|
||||
checkWhiteLabelsDefaultState,
|
||||
} from '@white-label/whiteLabelling';
|
||||
class OrganizationInvitationPageComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -21,10 +27,18 @@ class OrganizationInvitationPageComponent extends React.Component {
|
|||
this.organizationId = new URLSearchParams(props?.location?.search).get('oid');
|
||||
this.organizationToken = new URLSearchParams(props?.location?.search).get('organizationToken');
|
||||
this.source = new URLSearchParams(props?.location?.search).get('source');
|
||||
this.whiteLabelText = retrieveWhiteLabelText();
|
||||
this.whiteLabelFavicon = retrieveWhiteLabelFavicon();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
authenticationService.deleteLoginOrganizationId();
|
||||
setFaviconAndTitle(this.whiteLabelText, this.whiteLabelFavicon, this.props?.location);
|
||||
checkWhiteLabelsDefaultState(this.organizationId).then((res) => {
|
||||
this.setState({ defaultState: res });
|
||||
this.whiteLabelText = retrieveWhiteLabelText();
|
||||
this.whiteLabelFavicon = retrieveWhiteLabelFavicon();
|
||||
});
|
||||
document.addEventListener('keydown', this.handleEnterKey);
|
||||
}
|
||||
|
||||
|
|
@ -74,14 +88,14 @@ class OrganizationInvitationPageComponent extends React.Component {
|
|||
<form action="." method="get" autoComplete="off">
|
||||
<div className="common-auth-container-wrapper">
|
||||
<h2 className="common-auth-section-header org-invite-header" data-cy="invite-page-header">
|
||||
Join {organizationName ? organizationName : 'ToolJet'}
|
||||
Join {organizationName ? organizationName : this.whiteLabelText}
|
||||
</h2>
|
||||
|
||||
<div className="invite-sub-header" data-cy="invite-page-sub-header">
|
||||
{`You are invited to ${
|
||||
organizationName
|
||||
? `a workspace ${organizationName}. Accept the invite to join the workspace.`
|
||||
: 'ToolJet.'
|
||||
: this.whiteLabelText
|
||||
}`}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
display: flex !important;
|
||||
align-items: start !important;
|
||||
align-items: flex-start !important;
|
||||
justify-content: space-between !important;
|
||||
padding: 0.35rem !important;
|
||||
font-size: 11px !important;
|
||||
|
|
@ -533,9 +533,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.disabled-cursor{
|
||||
.disabled-cursor {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.disabled-pointerevents{
|
||||
|
||||
.disabled-pointerevents {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
@ -818,6 +818,8 @@ export const Container = ({
|
|||
? 'Connect to your data source or use our sample data source to start playing around!'
|
||||
: 'Connect to a data source to be able to create a query';
|
||||
|
||||
const showEmptyContainer = !appLoading && !isDragging && mode !== 'view';
|
||||
|
||||
return (
|
||||
<ContainerWrapper
|
||||
showComments={showComments}
|
||||
|
|
@ -930,7 +932,7 @@ export const Container = ({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
{Object.keys(boxes).length === 0 && !appLoading && !isDragging && (
|
||||
{Object.keys(boxes).length === 0 && showEmptyContainer && (
|
||||
<div style={{ paddingTop: '10%' }}>
|
||||
<div className="row empty-box-cont">
|
||||
<div className="col-md-4 dotted-cont">
|
||||
|
|
|
|||
|
|
@ -46,13 +46,8 @@ import { withTranslation } from 'react-i18next';
|
|||
import { v4 as uuid } from 'uuid';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import EditorHeader from './Header';
|
||||
import {
|
||||
getWorkspaceId,
|
||||
isValidUUID,
|
||||
setWindowTitle,
|
||||
defaultWhiteLabellingSettings,
|
||||
pageTitles,
|
||||
} from '@/_helpers/utils';
|
||||
import { getWorkspaceId, isValidUUID } from '@/_helpers/utils';
|
||||
import { fetchAndSetWindowTitle, pageTitles, defaultWhiteLabellingSettings } from '@white-label/whiteLabelling';
|
||||
import '@/_styles/editor/react-select-search.scss';
|
||||
import { withRouter } from '@/_hoc/withRouter';
|
||||
import { ReleasedVersionError } from './AppVersionsManager/ReleasedVersionError';
|
||||
|
|
@ -621,7 +616,7 @@ const EditorComponent = (props) => {
|
|||
app.name = newName;
|
||||
updateState({ appName: newName, app: app });
|
||||
updateState({ appName: newName });
|
||||
setWindowTitle({ page: pageTitles.EDITOR, appName: newName });
|
||||
fetchAndSetWindowTitle({ page: pageTitles.EDITOR, appName: newName });
|
||||
};
|
||||
|
||||
const onZoomChanged = (zoom) => {
|
||||
|
|
@ -760,7 +755,7 @@ const EditorComponent = (props) => {
|
|||
} = appData;
|
||||
|
||||
const startingPageHandle = props.params.pageHandle;
|
||||
setWindowTitle({ page: pageTitles.EDITOR, appName });
|
||||
fetchAndSetWindowTitle({ page: pageTitles.EDITOR, appName });
|
||||
useAppVersionStore.getState().actions.updateEditingVersion(editing_version);
|
||||
current_version_id && useAppVersionStore.getState().actions.updateReleasedVersionId(current_version_id);
|
||||
await fetchOrgEnvironmentConstants();
|
||||
|
|
|
|||
|
|
@ -14,11 +14,13 @@ import cx from 'classnames';
|
|||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import { TOOLTIP_MESSAGES } from '@/_helpers/constants';
|
||||
import { useAppDataStore } from '@/_stores/appDataStore';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
|
||||
class ManageAppUsersComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.isUserAdmin = authenticationService.currentSessionValue?.admin;
|
||||
this.whiteLabelText = retrieveWhiteLabelText();
|
||||
|
||||
this.state = {
|
||||
showModal: false,
|
||||
|
|
@ -175,7 +177,7 @@ class ManageAppUsersComponent extends React.Component {
|
|||
const appLink = `${getHostURL()}/applications/`;
|
||||
const shareableLink = appLink + (this.props.slug || appId);
|
||||
const slugButtonClass = !_.isEmpty(newSlug.error) ? 'is-invalid' : 'is-valid';
|
||||
const embeddableLink = `<iframe width="560" height="315" src="${appLink}${this.props.slug}" title="Tooljet app - ${this.props.slug}" frameborder="0" allowfullscreen></iframe>`;
|
||||
const embeddableLink = `<iframe width="560" height="315" src="${appLink}${this.props.slug}" title="${this.whiteLabelText} app - ${this.props.slug}" frameborder="0" allowfullscreen></iframe>`;
|
||||
const shouldWeDisableShareModal = !this.props.isVersionReleased;
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export default function EditorHeader({
|
|||
: '';
|
||||
setAppPreviewLink(appVersionPreviewLink);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [slug, currentVersionId, editingVersion]);
|
||||
}, [slug, currentVersionId, editingVersion, pageHandle]);
|
||||
|
||||
return (
|
||||
<div className={cx('header', { 'dark-theme theme-dark': darkMode })} style={{ width: '100%' }}>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import {
|
|||
import queryString from 'query-string';
|
||||
import ViewerLogoIcon from './Icons/viewer-logo.svg';
|
||||
import { DataSourceTypes } from './DataSourceManager/SourceComponents';
|
||||
import { resolveReferences, isQueryRunnable, setWindowTitle, pageTitles, isValidUUID } from '@/_helpers/utils';
|
||||
import { resolveReferences, isQueryRunnable, isValidUUID } from '@/_helpers/utils';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import _ from 'lodash';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
|
@ -50,6 +50,7 @@ import { findAllEntityReferences } from '@/_stores/utils';
|
|||
import { dfs } from '@/_stores/handleReferenceTransactions';
|
||||
import useAppDarkMode from '@/_hooks/useAppDarkMode';
|
||||
import TooljetBanner from './Viewer/TooljetBanner';
|
||||
import { fetchAndSetWindowTitle, pageTitles } from '@white-label/whiteLabelling';
|
||||
|
||||
class ViewerComponent extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -491,7 +492,7 @@ class ViewerComponent extends React.Component {
|
|||
useCurrentStateStore.getState().actions.initializeCurrentStateOnVersionSwitch();
|
||||
this.setStateForApp(data, true);
|
||||
this.setStateForContainer(data);
|
||||
setWindowTitle({
|
||||
fetchAndSetWindowTitle({
|
||||
page: pageTitles.VIEWER,
|
||||
appName: data.name,
|
||||
preview,
|
||||
|
|
@ -516,7 +517,7 @@ class ViewerComponent extends React.Component {
|
|||
await appService
|
||||
.fetchAppByVersion(appId, versionId)
|
||||
.then((data) => {
|
||||
setWindowTitle({
|
||||
fetchAndSetWindowTitle({
|
||||
page: pageTitles.VIEWER,
|
||||
appName: data.name,
|
||||
preview: true,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { ButtonSolid } from '@/_components/AppButton';
|
|||
import { withTranslation } from 'react-i18next';
|
||||
import EnterIcon from '../../assets/images/onboardingassets/Icons/Enter';
|
||||
import Spinner from '@/_ui/Spinner';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
class ForgotPasswordComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -21,6 +22,7 @@ class ForgotPasswordComponent extends React.Component {
|
|||
};
|
||||
}
|
||||
darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
whiteLabelText = retrieveWhiteLabelText();
|
||||
|
||||
handleChange = (event) => {
|
||||
this.setState({ [event.target.name]: event.target.value, emailError: '' });
|
||||
|
|
@ -68,9 +70,10 @@ class ForgotPasswordComponent extends React.Component {
|
|||
Forgot Password
|
||||
</h2>
|
||||
<p className="common-auth-sub-header" data-cy="forgot-password-sub-header">
|
||||
New to ToolJet?
|
||||
New to {this.whiteLabelText}?
|
||||
<Link
|
||||
to={'/signup'}
|
||||
state={{ from: '/forgot-password' }}
|
||||
tabIndex="-1"
|
||||
style={{ color: this.darkMode && '#3E63DD' }}
|
||||
data-cy="create-an-account-link"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { SearchBox } from '@/_components';
|
|||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import { BreadCrumbContext } from '@/App';
|
||||
import { pageTitles, setWindowTitle } from '@/_helpers/utils';
|
||||
import { fetchAndSetWindowTitle, pageTitles } from '@white-label/whiteLabelling';
|
||||
|
||||
export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasource }) => {
|
||||
const containerRef = useRef(null);
|
||||
|
|
@ -69,7 +69,7 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setWindowTitle({ page: `${selectedDataSource?.name || pageTitles.DATA_SOURCES}` });
|
||||
fetchAndSetWindowTitle({ page: `${selectedDataSource?.name || pageTitles.DATA_SOURCES}` });
|
||||
if (selectedDataSource) {
|
||||
setModalProps({ ...modalProps, backdrop: false });
|
||||
}
|
||||
|
|
@ -328,6 +328,7 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
|
|||
{datasources.map((item) => (
|
||||
<Card
|
||||
key={item.key}
|
||||
darkMode={darkMode}
|
||||
title={item.title}
|
||||
src={item?.src}
|
||||
usePluginIcon={isEmpty(item?.iconFile?.data)}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ export const List = ({ updateSelectedDatasource }) => {
|
|||
<EmptyFoldersIllustration />
|
||||
</div>
|
||||
<div className="tj-text-md text-secondary" data-cy="empty-ds-page-text">
|
||||
No datasources added
|
||||
{filteredData?.length === 0 && dataSources?.length !== 0 ? 'No results found' : 'No datasources added'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { GlobalDataSourcesPage } from './GlobalDataSourcesPage';
|
|||
import { toast } from 'react-hot-toast';
|
||||
import { BreadCrumbContext } from '@/App/App';
|
||||
import { DATA_SOURCE_TYPE } from '@/_helpers/constants';
|
||||
import { fetchAndSetWindowTitle, pageTitles } from '@white-label/whiteLabelling';
|
||||
|
||||
export const GlobalDataSourcesContext = createContext({
|
||||
showDataSourceManagerModal: false,
|
||||
|
|
@ -39,6 +40,7 @@ export const GlobalDatasources = (props) => {
|
|||
selectedDataSource
|
||||
? updateSidebarNAV(selectedDataSource.name)
|
||||
: !activeDatasourceList && updateSidebarNAV('Commonly used');
|
||||
fetchAndSetWindowTitle({ page: `${selectedDataSource?.name || pageTitles.DATA_SOURCES}` });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(dataSources), JSON.stringify(selectedDataSource)]);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import EmptyIllustration from '@assets/images/no-apps.svg';
|
|||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import EmptyFoldersIllustration from '@assets/images/icons/no-queries-added.svg';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
|
||||
export const BlankPage = function BlankPage({
|
||||
readAndImport,
|
||||
|
|
@ -17,6 +18,7 @@ export const BlankPage = function BlankPage({
|
|||
canCreateApp,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
const [deploying, setDeploying] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
|
@ -56,12 +58,17 @@ export const BlankPage = function BlankPage({
|
|||
<div className="row homepage-empty-container">
|
||||
<div className="col-7">
|
||||
<h3 className="empty-welcome-header" data-cy="empty-homepage-welcome-header">
|
||||
{t('blankPage.welcomeToToolJet', 'Welcome to your new ToolJet workspace')}
|
||||
{t('blankPage.welcomeToToolJet', `Welcome to your new ${whiteLabelText} workspace`, {
|
||||
whiteLabelText,
|
||||
})}
|
||||
</h3>
|
||||
<p className={`empty-title`} data-cy="empty-homepage-description">
|
||||
{t(
|
||||
'blankPage.getStartedCreateNewApp',
|
||||
'You can get started by creating a new application or by creating an application using a template in ToolJet Library.'
|
||||
`You can get started by creating a new application or by creating an application using a template in ${whiteLabelText} Library.`,
|
||||
{
|
||||
whiteLabelText,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
<div className="row mt-4">
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam
|
|||
const [versionId, setVersionId] = useState(undefined);
|
||||
const [exportTjDb, setExportTjDb] = useState(true);
|
||||
const [currentVersion, setCurrentVersion] = useState(undefined);
|
||||
const [versionSelectLoading, setVersionSelectLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -19,8 +20,11 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam
|
|||
setLoading(true);
|
||||
try {
|
||||
const fetchVersions = await appsService.getVersions(app.id);
|
||||
const fetchTables = await appsService.getTables(app.id); // this is used to get all tables
|
||||
const { versions } = fetchVersions;
|
||||
const { tables } = fetchTables;
|
||||
setVersions(versions);
|
||||
setAllTables(tables);
|
||||
const currentEditingVersion = versions?.filter((version) => version?.isCurrentEditingVersion)[0];
|
||||
if (currentEditingVersion) {
|
||||
setCurrentVersion(currentEditingVersion);
|
||||
|
|
@ -39,11 +43,9 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam
|
|||
|
||||
useEffect(() => {
|
||||
async function fetchAppTables() {
|
||||
setLoading(true);
|
||||
setVersionSelectLoading(true);
|
||||
try {
|
||||
if (!versionId) return;
|
||||
const fetchTables = await appsService.getTables(app.id); // this is used to get all tables
|
||||
const { tables } = fetchTables;
|
||||
const tbl = await appsService.getAppByVersion(app.id, versionId); // this is used to get particular App by version
|
||||
const { dataQueries } = tbl;
|
||||
const extractedIdData = [];
|
||||
|
|
@ -68,14 +70,13 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam
|
|||
const uniqueSet = new Set(extractedIdData);
|
||||
const selectedVersiontable = Array.from(uniqueSet).map((item) => ({ table_id: item }));
|
||||
setTables(selectedVersiontable);
|
||||
setAllTables(tables);
|
||||
} catch (error) {
|
||||
toast.error('Could not fetch the tables.', {
|
||||
position: 'top-center',
|
||||
});
|
||||
closeModal();
|
||||
}
|
||||
setLoading(false);
|
||||
setVersionSelectLoading(false);
|
||||
}
|
||||
fetchAppTables();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
@ -209,7 +210,7 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam
|
|||
Export All
|
||||
</ButtonSolid>
|
||||
<ButtonSolid
|
||||
className="import-export-footer-btns"
|
||||
className={`import-export-footer-btns ${versionSelectLoading ? 'btn-loading' : ''}`}
|
||||
data-cy="export-selected-version-button"
|
||||
onClick={() => exportApp(app, versionId, exportTjDb, tables)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -353,7 +353,7 @@ export const Folders = function Folders({
|
|||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col d-flex modal-footer-btn">
|
||||
<div className="col d-flex modal-footer-btn justify-content-end">
|
||||
<ButtonSolid variant="tertiary" onClick={closeModal} data-cy="cancel-button">
|
||||
{t('globals.cancel', 'Cancel')}
|
||||
</ButtonSolid>
|
||||
|
|
|
|||
|
|
@ -20,12 +20,13 @@ 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, pageTitles, setWindowTitle } from '@/_helpers/utils';
|
||||
import { getWorkspaceId } from '@/_helpers/utils';
|
||||
import { getQueryParams } from '@/_helpers/routes';
|
||||
import { withRouter } from '@/_hoc/withRouter';
|
||||
import FolderFilter from './FolderFilter';
|
||||
import { APP_ERROR_TYPE } from '@/_helpers/error_constants';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import { fetchAndSetWindowTitle, pageTitles } from '@white-label/whiteLabelling';
|
||||
|
||||
const { iconList, defaultIcon } = configs;
|
||||
|
||||
|
|
@ -87,7 +88,7 @@ class HomePageComponent extends React.Component {
|
|||
};
|
||||
|
||||
componentDidMount() {
|
||||
setWindowTitle({ page: pageTitles.DASHBOARD });
|
||||
fetchAndSetWindowTitle({ page: pageTitles.DASHBOARD });
|
||||
this.fetchApps(1, this.state.currentFolder.id);
|
||||
this.fetchFolders();
|
||||
this.setQueryParameter();
|
||||
|
|
@ -238,7 +239,13 @@ class HomePageComponent extends React.Component {
|
|||
};
|
||||
event.target.value = null;
|
||||
} catch (error) {
|
||||
toast.error(error.message);
|
||||
let errorMessage = 'Some Error Occured';
|
||||
if (error?.error) {
|
||||
errorMessage = error.error;
|
||||
} else if (error?.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -271,7 +278,7 @@ class HomePageComponent extends React.Component {
|
|||
if (error.statusCode === 409) {
|
||||
return false;
|
||||
}
|
||||
toast.error(error?.error || 'App import failed');
|
||||
toast.error(error?.error || error?.message || 'App import failed');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -720,7 +727,7 @@ class HomePageComponent extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col d-flex modal-footer-btn">
|
||||
<div className="col d-flex modal-footer-btn justify-content-end">
|
||||
<ButtonSolid
|
||||
variant="tertiary"
|
||||
onClick={() => this.setState({ showAddToFolderModal: false, appOperations: {} })}
|
||||
|
|
@ -750,7 +757,7 @@ class HomePageComponent extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col d-flex modal-footer-btn">
|
||||
<div className="col d-flex modal-footer-btn justify-content-end">
|
||||
<ButtonSolid
|
||||
onClick={() => this.setState({ showChangeIconModal: false, appOperations: {} })}
|
||||
data-cy="cancel-button"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { onLoginSuccess } from '@/_helpers/platform/utils/auth.utils';
|
|||
import { updateCurrentSession } from '@/_helpers/authorizeWorkspace';
|
||||
import cx from 'classnames';
|
||||
import SSOLoginModule from './SSOLoginModule';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
|
||||
class LoginPageComponent extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -31,6 +32,7 @@ class LoginPageComponent extends React.Component {
|
|||
this.paramOrganizationSlug = props?.params?.organizationId;
|
||||
}
|
||||
darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
whiteLabelText = retrieveWhiteLabelText();
|
||||
|
||||
componentDidMount() {
|
||||
/* remove login oranization's id and slug from the cookie */
|
||||
|
|
@ -130,7 +132,9 @@ class LoginPageComponent extends React.Component {
|
|||
const signUpCTA = workspaceSignUpEnabled ? 'Sign up' : 'Create an account';
|
||||
const signupText = workspaceSignUpEnabled
|
||||
? this.props.t('loginSignupPage.newToWorkspace', `New to this workspace?`)
|
||||
: this.props.t('loginSignupPage.newToTooljet', `New to Tooljet?`);
|
||||
: this.props.t('loginSignupPage.newToTooljet', ` New to ${this.whiteLabelText}?`, {
|
||||
whiteLabelText: this.whiteLabelText,
|
||||
});
|
||||
const signUpUrl = `/signup${this.paramOrganizationSlug ? `/${this.paramOrganizationSlug}` : ''}${
|
||||
redirectTo ? `?redirectTo=${redirectTo}` : ''
|
||||
}`;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import useRouter from '@/_hooks/use-router';
|
||||
import { authenticationService } from '@/_services';
|
||||
import { appService, authenticationService } from '@/_services';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import Configs from './Configs/Config.json';
|
||||
import { RedirectLoader } from '../_components';
|
||||
|
|
@ -85,7 +85,22 @@ export function Authorize({ navigate }) {
|
|||
const inviteeEmail = details?.inviteeEmail;
|
||||
if (inviteeEmail) setInviteeEmail(inviteeEmail);
|
||||
const errMessage = details?.message || err?.error || 'something went wrong';
|
||||
setError(`${configs.name} login failed - ${errMessage}`);
|
||||
if (!inviteeEmail && inviteFlowIdentifier) {
|
||||
/* Some unexpected error happened from the provider side. Need to retreive email to continue */
|
||||
appService
|
||||
.getInviteeDetails(inviteFlowIdentifier)
|
||||
.then((response) => {
|
||||
setInviteeEmail(response.email);
|
||||
})
|
||||
.catch(() => {
|
||||
console.error('Error while fetching invitee details');
|
||||
})
|
||||
.finally(() => {
|
||||
setError(`${configs.name} login failed - ${errMessage}`);
|
||||
});
|
||||
} else {
|
||||
setError(`${configs.name} login failed - ${errMessage}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import OnBoardingRadioInput from './OnBoardingRadioInput';
|
|||
import ContinueButton from './ContinueButton';
|
||||
import OnBoardingBubbles from './OnBoardingBubbles';
|
||||
import { getuserName } from '@/_helpers/utils';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
import { redirectToDashboard } from '@/_helpers/routes';
|
||||
import { ON_BOARDING_SIZE, ON_BOARDING_ROLES } from '@/_helpers/constants';
|
||||
import LogoLightMode from '@assets/images/Logomark.svg';
|
||||
|
|
@ -26,6 +27,7 @@ function OnBoardingForm({ userDetails = {}, token = '', organizationToken = '',
|
|||
phoneNumber: '',
|
||||
});
|
||||
const source = new URLSearchParams(location?.search).get('source');
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
|
||||
const pageProps = {
|
||||
formData,
|
||||
|
|
@ -75,7 +77,7 @@ function OnBoardingForm({ userDetails = {}, token = '', organizationToken = '',
|
|||
'Enter your phone number',
|
||||
'Enter your phone number', //dummy for styling
|
||||
];
|
||||
const FormSubTitles = ['This information will help us improve ToolJet.'];
|
||||
const FormSubTitles = [`This information will help us improve ${whiteLabelText}.`];
|
||||
|
||||
return (
|
||||
<div className="flex">
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@ import { useSessionManagement } from '@/_hooks/useSessionManagement';
|
|||
import { getRedirectURL, pathnameToArray } from '@/_helpers/routes';
|
||||
import { authenticationService } from '@/_services';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import {
|
||||
resetToDefaultWhiteLabels,
|
||||
retrieveWhiteLabelFavicon,
|
||||
retrieveWhiteLabelText,
|
||||
setFaviconAndTitle,
|
||||
} from '@white-label/whiteLabelling';
|
||||
|
||||
export const AuthRoute = ({ children, navigate }) => {
|
||||
const { isLoading, session, isValidSession, isInvalidSession, setLoading } = useSessionManagement({
|
||||
|
|
@ -29,8 +35,7 @@ export const AuthRoute = ({ children, navigate }) => {
|
|||
|
||||
useEffect(
|
||||
() => {
|
||||
authenticationService.deleteAllAuthCookies();
|
||||
fetchOrganizationDetails();
|
||||
initialize();
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[location.pathname]
|
||||
|
|
@ -43,11 +48,30 @@ export const AuthRoute = ({ children, navigate }) => {
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
const isComingFromPasswordReset = location?.state?.from === '/reset-password';
|
||||
const isComingFromPasswordReset =
|
||||
location?.state?.from === '/reset-password' || location?.state?.from === '/forgot-password';
|
||||
if ((isInvalidSession || isComingFromPasswordReset) && !isGettingConfigs) setLoading(false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isInvalidSession, isGettingConfigs]);
|
||||
|
||||
const initialize = () => {
|
||||
const pathname = location.pathname;
|
||||
authenticationService.deleteAllAuthCookies();
|
||||
fetchOrganizationDetails();
|
||||
verifyWhiteLabeling(pathname);
|
||||
};
|
||||
|
||||
const verifyWhiteLabeling = (pathname) => {
|
||||
const signupRegex = /^\/signup\/[^/]+$/;
|
||||
const loginRegex = /^\/login\/[^/]+$/;
|
||||
if (!signupRegex.test(pathname) && !loginRegex.test(pathname)) {
|
||||
resetToDefaultWhiteLabels();
|
||||
}
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
const whiteLabelFavicon = retrieveWhiteLabelFavicon();
|
||||
setFaviconAndTitle(whiteLabelFavicon, whiteLabelText, location);
|
||||
};
|
||||
|
||||
const fetchOrganizationDetails = () => {
|
||||
authenticationService.getOrganizationConfigs(organizationSlug).then(
|
||||
(configs) => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { RouteLoader } from './RouteLoader';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { authenticationService } from '@/_services';
|
||||
import { appService, authenticationService } from '@/_services';
|
||||
import { authorizeUserAndHandleErrors, updateCurrentSession } from '@/_helpers/authorizeWorkspace';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { LinkExpiredPage } from '@/ConfirmationPage/LinkExpiredPage';
|
||||
import { onLoginSuccess } from '@/_helpers/platform/utils/auth.utils';
|
||||
|
||||
export const OrganizationInviteRoute = ({ children, isOrgazanizationOnlyInvite, navigate }) => {
|
||||
/* Needed to pass invite token to signup page if the user doesn't exist */
|
||||
|
|
@ -15,6 +16,8 @@ export const OrganizationInviteRoute = ({ children, isOrgazanizationOnlyInvite,
|
|||
const organizationToken = params.organizationToken || (isOrgazanizationOnlyInvite ? params.token : null);
|
||||
const accountToken = !isOrgazanizationOnlyInvite ? params.token : null;
|
||||
const [extraProps, setExtraProps] = useState({});
|
||||
const searchParams = new URLSearchParams(location?.search);
|
||||
const redirectTo = searchParams.get('redirectTo');
|
||||
|
||||
useEffect(() => {
|
||||
getInvitedUserSession();
|
||||
|
|
@ -33,6 +36,8 @@ export const OrganizationInviteRoute = ({ children, isOrgazanizationOnlyInvite,
|
|||
name,
|
||||
organization_invite_url,
|
||||
is_workspace_sign_up_invite,
|
||||
source,
|
||||
organization_user_source,
|
||||
} = invitedUserSession;
|
||||
/*
|
||||
We should only run the authorization against the session if the user has active workspace
|
||||
|
|
@ -42,6 +47,10 @@ export const OrganizationInviteRoute = ({ children, isOrgazanizationOnlyInvite,
|
|||
email,
|
||||
name,
|
||||
});
|
||||
if (source === 'workspace_signup' || organization_user_source === 'signup') {
|
||||
acceptInvite(accountToken, organizationToken, navigate, source, redirectTo);
|
||||
return;
|
||||
}
|
||||
if (is_workspace_sign_up_invite) {
|
||||
setLoading(false);
|
||||
return;
|
||||
|
|
@ -141,6 +150,41 @@ export const OrganizationInviteRoute = ({ children, isOrgazanizationOnlyInvite,
|
|||
);
|
||||
};
|
||||
|
||||
const acceptInvite = (token, organizationToken, navigate, source, redirectTo) => {
|
||||
if (token && organizationToken) {
|
||||
authenticationService
|
||||
.onboarding({
|
||||
token,
|
||||
organizationToken,
|
||||
source,
|
||||
})
|
||||
.then((user) => {
|
||||
onLoginSuccess(user, navigate, redirectTo);
|
||||
})
|
||||
.catch((res) => {
|
||||
toast.error(res.error || 'Something went wrong', {
|
||||
id: 'toast-login-auth-error',
|
||||
position: 'top-center',
|
||||
});
|
||||
});
|
||||
} else {
|
||||
appService
|
||||
.acceptInvite({
|
||||
token: organizationToken,
|
||||
})
|
||||
.then((data) => {
|
||||
toast.success(`Added to the workspace successfully.`);
|
||||
updateCurrentSession({
|
||||
isUserLoggingIn: true,
|
||||
});
|
||||
onLoginSuccess(data, navigate);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Error while setting up your account.', { position: 'top-center' });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (invalidLink) return <LinkExpiredPage />;
|
||||
|
||||
const clonedElement = React.cloneElement(children || <></>, extraProps);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { extractErrorObj, onInvitedUserSignUpSuccess } from '@/_helpers/platform
|
|||
import { isEmpty } from 'lodash';
|
||||
import { EmailComponent } from './EmailComponent';
|
||||
import SSOLoginModule from '@/LoginPage/SSOLoginModule';
|
||||
import { checkWhiteLabelsDefaultState } from '@white-label/whiteLabelling';
|
||||
class SignupPageComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -34,6 +35,7 @@ class SignupPageComponent extends React.Component {
|
|||
emailError: '',
|
||||
disableOnEdit: false,
|
||||
email: this.inviteeEmail || '',
|
||||
defaultState: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -42,6 +44,9 @@ class SignupPageComponent extends React.Component {
|
|||
if (errorMessage) {
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
checkWhiteLabelsDefaultState(this.inviteOrganizationId).then((res) => {
|
||||
this.setState({ defaultState: res });
|
||||
});
|
||||
}
|
||||
|
||||
backtoSignup = (email, name) => {
|
||||
|
|
@ -122,7 +127,7 @@ class SignupPageComponent extends React.Component {
|
|||
|
||||
render() {
|
||||
const { configs } = this.props;
|
||||
const { isLoading, signupSuccess } = this.state;
|
||||
const { isLoading, signupSuccess, defaultState } = this.state;
|
||||
const comingFromInviteFlow = !!this.organizationToken;
|
||||
const isSignUpButtonDisabled =
|
||||
isLoading ||
|
||||
|
|
@ -320,20 +325,22 @@ class SignupPageComponent extends React.Component {
|
|||
</>
|
||||
)}
|
||||
|
||||
<p className="signup-terms" data-cy="signup-terms-helper">
|
||||
By signing up you are agreeing to the
|
||||
<br />
|
||||
<span>
|
||||
<a href="https://www.tooljet.com/terms" data-cy="terms-of-service-link">
|
||||
Terms of Service{' '}
|
||||
</a>
|
||||
&
|
||||
<a href="https://www.tooljet.com/privacy" data-cy="privacy-policy-link">
|
||||
{' '}
|
||||
Privacy Policy
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
{defaultState && (
|
||||
<p className="signup-terms" data-cy="signup-terms-helper">
|
||||
By signing up you are agreeing to the
|
||||
<br />
|
||||
<span>
|
||||
<a href="https://www.tooljet.com/terms" data-cy="terms-of-service-link">
|
||||
Terms of Service{' '}
|
||||
</a>
|
||||
&
|
||||
<a href="https://www.tooljet.com/privacy" data-cy="privacy-policy-link">
|
||||
{' '}
|
||||
Privacy Policy
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
@ -347,6 +354,8 @@ class SignupPageComponent extends React.Component {
|
|||
name={this.state.name}
|
||||
backtoSignup={this.backtoSignup}
|
||||
darkMode={this.darkMode}
|
||||
organizationId={this.inviteOrganizationId}
|
||||
redirectTo={this.redirectTo}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import React from 'react';
|
||||
import { ButtonSolid } from '@/_components/AppButton';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
|
||||
export const PasswordResetinfoScreen = function PasswordResetinfoScreen({ darkMode }) {
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div className="info-screen-wrapper">
|
||||
|
|
@ -22,7 +24,7 @@ export const PasswordResetinfoScreen = function PasswordResetinfoScreen({ darkMo
|
|||
Password has been reset
|
||||
</h1>
|
||||
<p className="info-screen-description" data-cy="reset-password-page-description">
|
||||
Your password has been reset successfully, log into ToolJet to continue your session
|
||||
Your password has been reset successfully, log into {whiteLabelText} to continue your session
|
||||
</p>
|
||||
<ButtonSolid
|
||||
variant="secondary"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,14 @@ import { authenticationService } from '@/_services';
|
|||
import { toast } from 'react-hot-toast';
|
||||
import Spinner from '@/_ui/Spinner';
|
||||
|
||||
export const SignupInfoScreen = function SignupInfoScreen({ email, backtoSignup, name, darkMode }) {
|
||||
export const SignupInfoScreen = function SignupInfoScreen({
|
||||
email,
|
||||
backtoSignup,
|
||||
name,
|
||||
darkMode,
|
||||
organizationId,
|
||||
redirectTo,
|
||||
}) {
|
||||
const [resendBtn, setResetBtn] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [buttonText, setButtonText] = useState('Resend verification mail in 30s');
|
||||
|
|
@ -30,7 +37,7 @@ export const SignupInfoScreen = function SignupInfoScreen({ email, backtoSignup,
|
|||
|
||||
e.preventDefault();
|
||||
authenticationService
|
||||
.resendInvite(email)
|
||||
.resendInvite(email, organizationId, redirectTo)
|
||||
.then(() => {
|
||||
setIsLoading(false);
|
||||
setResetBtn(true);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import EnterIcon from '../../assets/images/onboardingassets/Icons/Enter';
|
||||
import GoogleSSOLoginButton from '@ee/components/LoginPage/GoogleSSOLoginButton';
|
||||
import GitSSOLoginButton from '@ee/components/LoginPage/GitSSOLoginButton';
|
||||
import OnBoardingForm from '../OnBoardingForm/OnBoardingForm';
|
||||
import { authenticationService } from '@/_services';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { LinkExpiredInfoScreen } from '@/SuccessInfoScreen';
|
||||
import { ShowLoading } from '@/_components';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
|
@ -15,7 +13,9 @@ import EyeShow from '../../assets/images/onboardingassets/Icons/EyeShow';
|
|||
import Spinner from '@/_ui/Spinner';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { buildURLWithQuery } from '@/_helpers/utils';
|
||||
import { onLoginSuccess } from '@/_helpers/platform/utils/auth.utils';
|
||||
import { redirectToDashboard } from '@/_helpers/routes';
|
||||
import { retrieveWhiteLabelText, setFaviconAndTitle, checkWhiteLabelsDefaultState } from '@white-label/whiteLabelling';
|
||||
|
||||
export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScreen() {
|
||||
const [showOnboarding, setShowOnboarding] = useState(false);
|
||||
|
|
@ -29,6 +29,7 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [fallBack, setFallBack] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const [defaultState, setDefaultState] = useState(false);
|
||||
|
||||
const location = useLocation();
|
||||
const params = useParams();
|
||||
|
|
@ -39,6 +40,8 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
const source = searchParams.get('source');
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
const redirectTo = searchParams.get('redirectTo');
|
||||
const navigate = useNavigate();
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
|
||||
const getUserDetails = () => {
|
||||
setIsLoading(true);
|
||||
|
|
@ -81,6 +84,7 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
(configs) => {
|
||||
setIsGettingConfigs(false);
|
||||
setConfigs(configs);
|
||||
setFaviconAndTitle(null, null, location);
|
||||
},
|
||||
() => {
|
||||
setIsGettingConfigs(false);
|
||||
|
|
@ -89,6 +93,9 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
} else {
|
||||
setIsGettingConfigs(false);
|
||||
}
|
||||
checkWhiteLabelsDefaultState(organizationId).then((res) => {
|
||||
setDefaultState(res);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
|
|
@ -121,7 +128,7 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
.then((user) => {
|
||||
authenticationService.deleteLoginOrganizationId();
|
||||
setIsLoading(false);
|
||||
redirectToDashboard(user, redirectTo);
|
||||
onLoginSuccess(user, navigate, redirectTo);
|
||||
})
|
||||
.catch((res) => {
|
||||
setIsLoading(false);
|
||||
|
|
@ -180,14 +187,14 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
) : (
|
||||
<div className="common-auth-container-wrapper">
|
||||
<h2 className="common-auth-section-header org-invite-header" data-cy="invite-page-header">
|
||||
Join {configs?.name ? configs?.name : 'ToolJet'}
|
||||
Join {configs?.name ? configs?.name : whiteLabelText}
|
||||
</h2>
|
||||
|
||||
<div className="invite-sub-header" data-cy="invite-page-sub-header">
|
||||
{`You are invited to ${
|
||||
configs?.name
|
||||
? `a workspace ${configs?.name}. Accept the invite to join the workspace.`
|
||||
: 'ToolJet.'
|
||||
: `${whiteLabelText}.`
|
||||
}`}
|
||||
</div>
|
||||
|
||||
|
|
@ -288,20 +295,22 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
)}
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
<p className="verification-terms" data-cy="signup-terms-helper">
|
||||
By signing up you are agreeing to the
|
||||
<br />
|
||||
<span>
|
||||
<a href="https://www.tooljet.com/terms" data-cy="terms-of-service-link">
|
||||
Terms of Service{' '}
|
||||
</a>
|
||||
&
|
||||
<a href="https://www.tooljet.com/privacy" data-cy="privacy-policy-link">
|
||||
{' '}
|
||||
Privacy Policy
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
{defaultState && (
|
||||
<p className="verification-terms" data-cy="signup-terms-helper">
|
||||
By signing up you are agreeing to the
|
||||
<br />
|
||||
<span>
|
||||
<a href="https://www.tooljet.com/terms" data-cy="terms-of-service-link">
|
||||
Terms of Service{' '}
|
||||
</a>
|
||||
&
|
||||
<a href="https://www.tooljet.com/privacy" data-cy="privacy-policy-link">
|
||||
{' '}
|
||||
Privacy Policy
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
|
|
@ -331,7 +340,7 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
{t('verificationSuccessPage.successfullyVerifiedEmail', 'Successfully verified email')}
|
||||
</h1>
|
||||
<p className="info-screen-description" data-cy="onboarding-page-description">
|
||||
Continue to set up your workspace to start using ToolJet.
|
||||
Continue to set up your workspace to start using {whiteLabelText}.
|
||||
</p>
|
||||
<ButtonSolid
|
||||
className="verification-success-info-btn "
|
||||
|
|
@ -347,7 +356,10 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
</div>
|
||||
) : (
|
||||
<>
|
||||
{t('verificationSuccessPage.setupTooljet', 'Set up ToolJet')}
|
||||
{t('verificationSuccessPage.setupTooljet', `Set up ${whiteLabelText}`, {
|
||||
whiteLabelText,
|
||||
})}
|
||||
|
||||
<EnterIcon fill={'#fff'}></EnterIcon>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
import React from 'react';
|
||||
import List from './List';
|
||||
import ListGroup from 'react-bootstrap/ListGroup';
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'List',
|
||||
component: List,
|
||||
parameters: {
|
||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout
|
||||
layout: 'centered',
|
||||
},
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs
|
||||
// tags: ['autodocs'],
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
};
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
export const WithIcon = {
|
||||
render: (args) => (
|
||||
<List>
|
||||
<List.Item>Client side</List.Item>
|
||||
<List.Item active>Server side</List.Item>
|
||||
<List.Item>Client side</List.Item>
|
||||
<List.Item disabled>Server side</List.Item>
|
||||
<List.Item>Client side</List.Item>
|
||||
<List.Item>Server side</List.Item>
|
||||
<List.Item>Client side</List.Item>
|
||||
<List.Item>Server side</List.Item>
|
||||
</List>
|
||||
),
|
||||
};
|
||||
|
||||
export const WithIconAndEdit = {
|
||||
render: (args) => (
|
||||
<ListGroup>
|
||||
<ListGroup.Item>Cras justo odio</ListGroup.Item>
|
||||
<ListGroup.Item>Dapibus ac facilisis in</ListGroup.Item>
|
||||
<ListGroup.Item>Morbi leo risus</ListGroup.Item>
|
||||
<ListGroup.Item>Porta ac consectetur ac</ListGroup.Item>
|
||||
<ListGroup.Item>Vestibulum at eros</ListGroup.Item>
|
||||
</ListGroup>
|
||||
),
|
||||
};
|
||||
|
|
@ -5,7 +5,7 @@ import ToggleGroupItem from './ToggleGroupItem';
|
|||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'ToggleGroup',
|
||||
title: 'Components/ToggleGroup',
|
||||
component: ToggleGroup,
|
||||
parameters: {
|
||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
import { Tabs } from './Tabs';
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'Tabs',
|
||||
component: Tabs,
|
||||
parameters: {
|
||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout
|
||||
layout: 'centered',
|
||||
},
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs
|
||||
tags: ['autodocs'],
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
};
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
export const Primary = {
|
||||
args: {
|
||||
primary: true,
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Secondary = {
|
||||
args: {
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Large = {
|
||||
args: {
|
||||
size: 'large',
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Small = {
|
||||
args: {
|
||||
size: 'small',
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
|
@ -85,7 +85,7 @@
|
|||
background-color: #FFF8F7;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
align-items: start;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #FDD8D3;
|
||||
|
|
@ -366,7 +366,7 @@
|
|||
.table-schema-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
|
||||
input.form-control {
|
||||
|
|
@ -494,7 +494,7 @@
|
|||
|
||||
.key-changes-container {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
padding: 12px;
|
||||
font-weight: 500;
|
||||
|
|
@ -554,15 +554,18 @@
|
|||
background: none !important;
|
||||
color: var(--slate9) !important;
|
||||
cursor: auto !important;
|
||||
svg{
|
||||
path:first-child{
|
||||
|
||||
svg {
|
||||
path:first-child {
|
||||
fill: var(--slate7) !important;
|
||||
}
|
||||
path:nth-child(2){
|
||||
|
||||
path:nth-child(2) {
|
||||
fill: var(--base) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled:hover {
|
||||
background: var(--slate3) !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import TooljetDatabasePage from './TooljetDatabasePage';
|
|||
import { usePostgrestQueryBuilder } from './usePostgrestQueryBuilder';
|
||||
import { authenticationService } from '../_services/authentication.service';
|
||||
import { BreadCrumbContext } from '@/App/App';
|
||||
import { pageTitles, setWindowTitle } from '@/_helpers/utils';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { pageTitles, fetchAndSetWindowTitle } from '@white-label/whiteLabelling';
|
||||
|
||||
export const TooljetDatabaseContext = createContext({
|
||||
organizationId: null,
|
||||
|
|
@ -156,7 +156,7 @@ export const TooljetDatabase = (props) => {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setWindowTitle({ page: `${selectedTable?.table_name || pageTitles.DATABASE}` });
|
||||
fetchAndSetWindowTitle({ page: `${selectedTable?.table_name || pageTitles.DATABASE}` });
|
||||
}, [selectedTable]);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import React from 'react';
|
||||
import Logo from '@assets/images/rocket.svg';
|
||||
import { retrieveWhiteLabelLogo } from '@white-label/whiteLabelling';
|
||||
|
||||
export default function AppLogo({ isLoadingFromHeader, className }) {
|
||||
const url = window.public_config?.WHITE_LABEL_LOGO;
|
||||
const url = retrieveWhiteLabelLogo();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -90,7 +90,13 @@ export function AppModal({
|
|||
closeModal();
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(e.error, {
|
||||
let errorMessage = 'Some Error Occured';
|
||||
if (error?.error) {
|
||||
errorMessage = error.error;
|
||||
} else if (error?.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
toast.error(errorMessage, {
|
||||
position: 'top-center',
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ import React, { useState } from 'react';
|
|||
import { datasourceService } from '@/_services';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
|
||||
import Radio from '@/_ui/Radio';
|
||||
import Button from '@/_ui/Button';
|
||||
|
||||
const Googlesheets = ({ optionchanged, createDataSource, options, isSaving, selectedDataSource }) => {
|
||||
const [authStatus, setAuthStatus] = useState(null);
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
const { t } = useTranslation();
|
||||
|
||||
function authGoogle() {
|
||||
|
|
@ -53,7 +55,8 @@ const Googlesheets = ({ optionchanged, createDataSource, options, isSaving, sele
|
|||
<p data-cy="google-sheet-connection-form-description">
|
||||
{t(
|
||||
'googleSheets.enableReadAndWrite',
|
||||
'If you want your ToolJet apps to modify your Google sheets, make sure to select read and write access'
|
||||
'If you want your ${whiteLabelText} apps to modify your Google sheets, make sure to select read and write access',
|
||||
{ whiteLabelText }
|
||||
)}
|
||||
</p>
|
||||
<div>
|
||||
|
|
@ -64,7 +67,8 @@ const Googlesheets = ({ optionchanged, createDataSource, options, isSaving, sele
|
|||
text={t('googleSheets.readOnly', 'Read only')}
|
||||
helpText={t(
|
||||
'googleSheets.readDataFromSheets',
|
||||
'Your ToolJet apps can only read data from Google sheets'
|
||||
'Your ${whiteLabelText} apps can only read data from Google sheets',
|
||||
{ whiteLabelText }
|
||||
)}
|
||||
/>
|
||||
<Radio
|
||||
|
|
@ -74,7 +78,8 @@ const Googlesheets = ({ optionchanged, createDataSource, options, isSaving, sele
|
|||
text={t('googleSheets.readWrite', 'Read and write')}
|
||||
helpText={t(
|
||||
'googleSheets.readModifySheets',
|
||||
'Your ToolJet apps can read data from sheets, modify sheets, and more.'
|
||||
'Your ${whiteLabelText} apps can read data from sheets, modify sheets, and more.',
|
||||
{ whiteLabelText }
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ import { datasourceService } from '@/_services';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import Button from '@/_ui/Button';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
|
||||
const Slack = ({ optionchanged, createDataSource, options, isSaving, _selectedDataSource }) => {
|
||||
const [authStatus, setAuthStatus] = useState(null);
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
const { t } = useTranslation();
|
||||
|
||||
function authGoogle() {
|
||||
|
|
@ -51,7 +53,8 @@ const Slack = ({ optionchanged, createDataSource, options, isSaving, _selectedDa
|
|||
<p>
|
||||
{t(
|
||||
'slack.connectToolJetToSlack',
|
||||
'ToolJet can connect to Slack and list users, send messages, etc. Please select appropriate permission scopes.'
|
||||
'${whiteLabelText} can connect to Slack and list users, send messages, etc. Please select appropriate permission scopes.',
|
||||
{ whiteLabelText }
|
||||
)}
|
||||
</p>
|
||||
<div>
|
||||
|
|
@ -68,7 +71,8 @@ const Slack = ({ optionchanged, createDataSource, options, isSaving, _selectedDa
|
|||
<small className="text-muted">
|
||||
{t(
|
||||
'slack.listUsersAndSendMessage',
|
||||
'Your ToolJet app will be able to list users and send messages to users & channels.'
|
||||
'Your ${whiteLabelText} app will be able to list users and send messages to users & channels.',
|
||||
{ whiteLabelText }
|
||||
)}
|
||||
</small>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import Input from '@/_ui/Input';
|
|||
import Radio from '@/_ui/Radio';
|
||||
import Button from '@/_ui/Button';
|
||||
import EncryptedFieldWrapper from './EncyrptedFieldWrapper';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
|
||||
const Zendesk = ({
|
||||
optionchanged,
|
||||
|
|
@ -15,6 +16,7 @@ const Zendesk = ({
|
|||
optionsChanged,
|
||||
}) => {
|
||||
const [authStatus, setAuthStatus] = useState(null);
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
|
||||
function authZendesk() {
|
||||
const provider = 'zendesk';
|
||||
|
|
@ -91,7 +93,7 @@ const Zendesk = ({
|
|||
<div className="mb-3">
|
||||
<div className="form-label">Scope(s)</div>
|
||||
<p>
|
||||
If you want your ToolJet apps to modify your Zendesk resources, make sure to select read and write access
|
||||
{`If you want your ${whiteLabelText} apps to modify your Zendesk resources, make sure to select read and write access`}
|
||||
</p>
|
||||
<div>
|
||||
<Radio
|
||||
|
|
@ -99,14 +101,14 @@ const Zendesk = ({
|
|||
disabled={authStatus === 'waiting_for_token'}
|
||||
onClick={() => optionchanged('access_type', 'read')}
|
||||
text="Read only"
|
||||
helpText="Your ToolJet apps can only read data from resources"
|
||||
helpText={`Your ${whiteLabelText} apps can only read data from resources`}
|
||||
/>
|
||||
<Radio
|
||||
checked={options?.access_type?.value === 'write'}
|
||||
disabled={authStatus === 'waiting_for_token'}
|
||||
onClick={() => optionchanged('access_type', 'write')}
|
||||
text="Read and write"
|
||||
helpText="Your ToolJet apps can read data from resources, modify resources, and more."
|
||||
helpText={`Your ${whiteLabelText} apps can read data from resources, modify resources, and more.`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1453,6 +1453,18 @@ export const getSvgIcon = (key, height = 50, width = 50, iconFile = undefined, s
|
|||
if (key === 'runjs') return <RunjsIcon style={{ height, width }} />;
|
||||
if (key === 'tooljetdb') return <RunTooljetDbIcon style={{ height, width }} />;
|
||||
if (key === 'runpy') return <RunPyIcon style={{ height, width }} />;
|
||||
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
//Add darkMode icons in allSvgs if needed ending with Dark
|
||||
if (darkMode) {
|
||||
const darkSrc = `${key}Dark`;
|
||||
if (allSvgs[darkSrc]) {
|
||||
key = darkSrc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Icon = allSvgs[key];
|
||||
|
||||
if (!Icon) return <></>;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export const onLoginSuccess = (userResponse, navigate, redirectTo = null) => {
|
|||
...restResponse,
|
||||
authentication_status: null,
|
||||
noWorkspaceAttachedInTheSession,
|
||||
isOrgSwitchingFailed: null,
|
||||
});
|
||||
const redirectPath = redirectTo || getCookie('redirectPath');
|
||||
const path = getRedirectURL(redirectPath);
|
||||
|
|
|
|||
|
|
@ -1235,71 +1235,6 @@ export const humanizeifDefaultGroupName = (groupName) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const defaultWhiteLabellingSettings = {
|
||||
WHITE_LABEL_LOGO: 'https://app.tooljet.com/logo.svg',
|
||||
WHITE_LABEL_TEXT: 'ToolJet',
|
||||
WHITE_LABEL_FAVICON: 'https://app.tooljet.com/favico.png',
|
||||
};
|
||||
|
||||
export const pageTitles = {
|
||||
INSTANCE_SETTINGS: 'Settings',
|
||||
WORKSPACE_SETTINGS: 'Workspace settings',
|
||||
INTEGRATIONS: 'Marketplace',
|
||||
WORKFLOWS: 'Workflows',
|
||||
DATABASE: 'Database',
|
||||
DATA_SOURCES: 'Data sources',
|
||||
AUDIT_LOGS: 'Audit logs',
|
||||
ACCOUNT_SETTINGS: 'Profile settings',
|
||||
SETTINGS: 'Profile settings',
|
||||
EDITOR: 'Editor',
|
||||
WORKFLOW_EDITOR: 'workflowEditor',
|
||||
VIEWER: 'Viewer',
|
||||
DASHBOARD: 'Dashboard',
|
||||
WORKSPACE_CONSTANTS: 'Workspace constants',
|
||||
};
|
||||
|
||||
export const setWindowTitle = async (pageDetails, location) => {
|
||||
const isEditorOrViewerGoingToRender = ['/apps/', '/applications/'].some((path) => location?.pathname.includes(path));
|
||||
const pathToTitle = {
|
||||
'instance-settings': pageTitles.INSTANCE_SETTINGS,
|
||||
'workspace-settings': pageTitles.WORKSPACE_SETTINGS,
|
||||
integrations: pageTitles.INTEGRATIONS,
|
||||
workflows: pageTitles.WORKFLOWS,
|
||||
database: pageTitles.DATABASE,
|
||||
'data-sources': pageTitles.DATA_SOURCES,
|
||||
'audit-logs': pageTitles.AUDIT_LOGS,
|
||||
'account-settings': pageTitles.ACCOUNT_SETTINGS,
|
||||
settings: pageTitles.SETTINGS,
|
||||
'workspace-constants': pageTitles.WORKSPACE_CONSTANTS,
|
||||
};
|
||||
const whiteLabelText = defaultWhiteLabellingSettings.WHITE_LABEL_TEXT;
|
||||
let pageTitleKey = pageDetails?.page || '';
|
||||
let pageTitle = '';
|
||||
if (!pageTitleKey && !isEditorOrViewerGoingToRender) {
|
||||
pageTitleKey = Object.keys(pathToTitle).find((path) => location?.pathname?.includes(path)) || '';
|
||||
}
|
||||
switch (pageTitleKey) {
|
||||
case pageTitles.VIEWER: {
|
||||
const titlePrefix = pageDetails?.preview ? 'Preview - ' : '';
|
||||
pageTitle = `${titlePrefix}${pageDetails?.appName || 'My App'}`;
|
||||
break;
|
||||
}
|
||||
case pageTitles.EDITOR:
|
||||
case pageTitles.WORKFLOW_EDITOR: {
|
||||
pageTitle = pageDetails?.appName || 'My App';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pageTitle = pathToTitle[pageTitleKey] || pageTitleKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pageTitle) {
|
||||
document.title = !(pageDetails?.preview === false)
|
||||
? `${decodeEntities(pageTitle)} | ${whiteLabelText}`
|
||||
: `${pageTitle}`;
|
||||
}
|
||||
};
|
||||
// This function is written only to handle diff colors W.R.T button types
|
||||
export const computeColor = (styleDefinition, value, meta) => {
|
||||
if (styleDefinition.type?.value == 'primary') return value;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { useParams, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { setWindowTitle } from '@/_helpers/utils';
|
||||
import { retrieveWhiteLabelFavicon, retrieveWhiteLabelText, setFaviconAndTitle } from '@white-label/whiteLabelling';
|
||||
|
||||
export const withRouter = (WrappedComponent) => (props) => {
|
||||
const params = useParams();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const whiteLabelFavicon = retrieveWhiteLabelFavicon();
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
|
||||
useEffect(() => {
|
||||
setWindowTitle(null, location);
|
||||
}, [location]);
|
||||
setFaviconAndTitle(whiteLabelFavicon, whiteLabelText, location);
|
||||
}, [whiteLabelFavicon, whiteLabelText, location]);
|
||||
|
||||
return <WrappedComponent {...props} params={params} location={location} navigate={navigate} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { authenticationService } from '@/_services';
|
||||
import { getWorkspaceId, setWindowTitle } from '@/_helpers/utils';
|
||||
import { getWorkspaceId } from '@/_helpers/utils';
|
||||
import { retrieveWhiteLabelFavicon, retrieveWhiteLabelText, setFaviconAndTitle } from '@white-label/whiteLabelling';
|
||||
import { appendWorkspaceId, excludeWorkspaceIdFromURL } from '@/_helpers/routes';
|
||||
|
||||
/*
|
||||
|
|
@ -26,6 +27,8 @@ export const useSessionManagement = (initialState = defaultState) => {
|
|||
const navigate = useNavigate();
|
||||
const { pathname, search, state } = location;
|
||||
const { disableValidSessionCallback, disableInValidSessionCallback } = initialState;
|
||||
const whiteLabelFavicon = retrieveWhiteLabelFavicon();
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
|
||||
useEffect(() => {
|
||||
/* replacing the state. otherwise the route will keep isSwitchingPage value `true` */
|
||||
|
|
@ -41,7 +44,7 @@ export const useSessionManagement = (initialState = defaultState) => {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setWindowTitle(null, location);
|
||||
setFaviconAndTitle(whiteLabelFavicon, whiteLabelText, location);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathname]);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export const appService = {
|
|||
createAppUser,
|
||||
setPasswordFromToken,
|
||||
acceptInvite,
|
||||
getInviteeDetails,
|
||||
};
|
||||
|
||||
function getConfig() {
|
||||
|
|
@ -202,3 +203,8 @@ function acceptInvite({ token, password }) {
|
|||
const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) };
|
||||
return fetch(`${config.apiUrl}/accept-invite`, requestOptions).then(handleResponseWithoutValidation);
|
||||
}
|
||||
|
||||
function getInviteeDetails(token) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/invitee-details?token=${token}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,11 +185,11 @@ function activateAccountWithToken(email, password, organizationToken) {
|
|||
return fetch(`${config.apiUrl}/activate-account-with-token`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function resendInvite(email) {
|
||||
function resendInvite(email, organizationId, redirectTo) {
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email }),
|
||||
body: JSON.stringify({ email, organizationId, redirectTo }),
|
||||
};
|
||||
|
||||
return fetch(`${config.apiUrl}/resend-invite`, requestOptions)
|
||||
|
|
|
|||
216
frontend/src/_styles/componentdesign.scss
Normal file
216
frontend/src/_styles/componentdesign.scss
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
:root {
|
||||
//page
|
||||
--page-default: #F6F8FA;
|
||||
--page-weak: #FFFFFF;
|
||||
|
||||
|
||||
//background
|
||||
--background-surface-layer-01: #FFFFFF;
|
||||
--background-surface-layer-02: #F6F8FA;
|
||||
--background-surface-layer-03: #E4E7EB;
|
||||
--background-accent-strong: #4368E3;
|
||||
--background-accent-weak: #ECF0FE;
|
||||
--background-success-strong: #1E823B;
|
||||
--background-success-weak: #E8F3EB;
|
||||
--background-error-strong: #D72D39;
|
||||
--background-error-weak: #FCEEEF;
|
||||
--background-warning-strong: #BF4F03;
|
||||
--background-warning-weak: #FAEFE7;
|
||||
--background-inverse: #1B1F24;
|
||||
|
||||
|
||||
//text
|
||||
--text-default: #1B1F24;
|
||||
--text-medium: #2D343B;
|
||||
--text-placeholder: #6A727C;
|
||||
--text-inverse: #F6F8FA;
|
||||
--text-brand: #4368E3;
|
||||
--text-selected: #4368E3;
|
||||
--text-success: #1E823B;
|
||||
--text-warning: #BF4F03;
|
||||
--text-danger: #D72D39;
|
||||
--text-on-solid: #FFFFFF;
|
||||
--text-disabled: #ACB2B9;
|
||||
--text-disabled-on-solid: #FFFFFF99;
|
||||
|
||||
|
||||
//icon
|
||||
--icon-strong: #6A727C;
|
||||
--icon-default: #ACB2B9;
|
||||
--icon-weak: #CCD1D5;
|
||||
--icon-inverse: #FFFFFF;
|
||||
--icon-on-solid: #FFFFFF;
|
||||
--icon-accent: #4368E3;
|
||||
--icon-brand: #4368E3;
|
||||
--icon-success: #1E823B;
|
||||
--icon-warning: #BF4F03;
|
||||
--icon-danger: #D72D39;
|
||||
--icon-disabled: #E4E7EB;
|
||||
|
||||
|
||||
//border
|
||||
--border-strong: #ACB2B9;
|
||||
--border-default: #CCD1D5;
|
||||
--border-weak: #E4E7EB;
|
||||
--border-accent-strong: #4368E3;
|
||||
--border-accent-weak: #97AEFC;
|
||||
--border-success-strong: #1E823B;
|
||||
--border-success-weak: #7FBF92;
|
||||
--border-warning-strong: #BF4F03;
|
||||
--border-warning-weak: #E7A274;
|
||||
--border-danger-strong: #D72D39;
|
||||
--border-danger-weak: #F4979E;
|
||||
--border-disabled: #F6F8FA;
|
||||
|
||||
|
||||
//interactive overlays
|
||||
--interactive-weak: #FFFFFF00;
|
||||
--interactive-default: #CCD1D54D;
|
||||
--interactive-hover: #ACB2B959;
|
||||
--interactive-selected: #ACB2B973;
|
||||
--interactive-disabled: #FFFFFF00;
|
||||
--interactive-focus-outline: #4368E3;
|
||||
--interactive-focus-active: #4368E3;
|
||||
--interactive-focus-inner-shadow: #FFFFFF;
|
||||
|
||||
|
||||
//button
|
||||
--button-primary: #4368E3;
|
||||
--button-primary-hover: #2D50C5;
|
||||
--button-primary-pressed: #1E3C9E;
|
||||
--button-primary-disabled: #C2CFFD;
|
||||
--button-secondary: #FFFFFF;
|
||||
--button-secondary-hover: #ECF0FE;
|
||||
--button-secondary-pressed: #DEE5FE;
|
||||
--button-secondary-disabled: #FFFFFF;
|
||||
--button-outline: #FFFFFF;
|
||||
--button-outline-hover: #ACB2B94D;
|
||||
--button-outline-pressed: #ACB2B966;
|
||||
--button-outline-disabled: #FFFFFF;
|
||||
--button-danger-primary: #D72D39;
|
||||
--button-danger-primary-hover: #B5121D;
|
||||
--button-danger-primary-pressed: #8E0811;
|
||||
--button-danger-primary-disabled: #F9C2C6;
|
||||
--button-danger-secondary: #FFFFFF;
|
||||
--button-danger-secondary-hover: #FCEEEF;
|
||||
--button-danger-secondary-pressed: #FBDFE1;
|
||||
--button-danger-secondary-disabled: #FFFFFF;
|
||||
|
||||
|
||||
//controls
|
||||
--switch-tab: #ffffff;
|
||||
--switch-tag: #CCD1D54D;
|
||||
--switch-background-off: #FFFFFF;
|
||||
--switch-background-on: #4368E3;
|
||||
--slider-handle: #FFFFFF;
|
||||
--slider-track: #E4E7EB;
|
||||
--slider-fill: #4368E3;
|
||||
}
|
||||
|
||||
.dark-theme {
|
||||
//page
|
||||
--page-default: #181B1F;
|
||||
--page-weak: #1E2226;
|
||||
|
||||
|
||||
//background
|
||||
--background-surface-layer-01: #1E2226;
|
||||
--background-surface-layer-02: #2B3036;
|
||||
--background-surface-layer-03: #3C434B;
|
||||
--background-accent-strong: #4A6DD9;
|
||||
--background-accent-weak: #0D183C;
|
||||
--background-success-strong: #318344;
|
||||
--background-success-weak: #05200B;
|
||||
--background-warning-strong: #BA5722;
|
||||
--background-warning-weak: #301100;
|
||||
--background-error-strong: #D03F43;
|
||||
--background-error-weak: #390809;
|
||||
--background-Inverse: #FAFCFF;
|
||||
|
||||
|
||||
//text
|
||||
--text-default: #FAFCFF;
|
||||
--text-medium: #CFD3D8;
|
||||
--text-placeholder: #858C94;
|
||||
--text-inverse: #181B1F;
|
||||
--text-accent: #4A6DD9;
|
||||
--text-success: #318344;
|
||||
--text-warning: #BA5722;
|
||||
--text-danger: #D03F43;
|
||||
--text-on-solid: #FFFFFF;
|
||||
--text-disabled: #545B64;
|
||||
--text-disabled-on-solid: #FAFCFF66;
|
||||
|
||||
|
||||
//icon
|
||||
--icon-strong: #CFD3D8E5;
|
||||
--icon-default: #CFD3D8A6;
|
||||
--icon-weak: #CFD3D859;
|
||||
--icon-inverse: #181B1F;
|
||||
--icon-on-solid: #FAFCFF;
|
||||
--icon-accent: #4A6DD9;
|
||||
--icon-brand: #4A6DD9;
|
||||
--icon-success: #1E823B;
|
||||
--icon-warning: #BA5722;
|
||||
--icon-danger: #D03F43;
|
||||
--icon-disabled: #CFD3D833;
|
||||
|
||||
|
||||
//border
|
||||
--border-strong: #545B64;
|
||||
--border-default: #3C434B;
|
||||
--border-weak: #2B3036;
|
||||
--border-accent-strong: #4A6DD9;
|
||||
--border-accent-weak: #4A6DD966;
|
||||
--border-success-strong: #519B62;
|
||||
--border-success-weak: #31834466;
|
||||
--border-warning-strong: #BA5722;
|
||||
--border-warning-weak: #BA572266;
|
||||
--border-danger-strong: #E26367;
|
||||
--border-danger-weak: #D03F4366;
|
||||
--border-disabled: #2B3036B2;
|
||||
|
||||
|
||||
//interactive overlays
|
||||
--interactive-weak: #00000000;
|
||||
--interactive-default: #A1A7AE1F;
|
||||
--interactive-hover: #A1A7AE29;
|
||||
--interactive-selected: #A1A7AE38;
|
||||
--interactive-disabled: #00000000;
|
||||
--interactive-focus-outline: #4A6DD9;
|
||||
--interactive-focus-inner-shadow: #121518;
|
||||
|
||||
|
||||
//button
|
||||
//button
|
||||
--button-primary: #4A6DD9;
|
||||
--button-primary-hover: #6787E8;
|
||||
--button-primary-pressed: #8AA3F2;
|
||||
--button-primary-disabled: #162A69;
|
||||
--button-secondary: #1E2226;
|
||||
--button-secondary-hover: #162A69;
|
||||
--button-secondary-pressed: #213C90;
|
||||
--button-secondary-disabled: #121518;
|
||||
--button-outline: #A1A7AE1A;
|
||||
--button-outline-hover: #A1A7AE33;
|
||||
--button-outline-pressed: #A1A7AE4D;
|
||||
--button-outline-disabled: #121518;
|
||||
--button-danger-primary: #D03F43;
|
||||
--button-danger-primary-hover: #E26367;
|
||||
--button-danger-primary-pressed: #EC8B8E;
|
||||
--button-danger-primary-disabled: #620D0F;
|
||||
--button-danger-secondary: #121518;
|
||||
--button-danger-secondary-hover: #620D0F;
|
||||
--button-danger-secondary-pressed: #841417;
|
||||
--button-danger-secondary-disabled: #121518;
|
||||
|
||||
|
||||
//controls
|
||||
--switch-tab: #2B3036;
|
||||
--switch-tag: #121518;
|
||||
--switch-background-off: #121518;
|
||||
--switch-background-on: #4A6DD9;
|
||||
--slider-handle: #121518;
|
||||
--slider-track: #2B3036;
|
||||
--slider-fill: #4A6DD9;
|
||||
}
|
||||
|
|
@ -56,6 +56,7 @@
|
|||
width: 262px;
|
||||
height: 32px;
|
||||
border: none !important;
|
||||
background-color: var(--page-default) !important;
|
||||
|
||||
&:hover {
|
||||
background: var(--slate2) !important;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
.global-datasources-sidebar {
|
||||
height: calc(100vh - 64px);
|
||||
max-width: 288px;
|
||||
background: var(--base);
|
||||
background: var(--page-default);
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
border-right: 1px solid var(--slate5);
|
||||
|
|
@ -105,7 +105,7 @@
|
|||
|
||||
.datasource-modal-container {
|
||||
position: relative;
|
||||
// background: var(--slate2);
|
||||
background: var(--page-default);
|
||||
|
||||
.modal-header {
|
||||
background-color: var(--slate3) !important;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
@import "./colors.scss";
|
||||
@import "./designtheme.scss";
|
||||
|
||||
.left-sidebar {
|
||||
background: var(--base) !important;
|
||||
background: var(--page-default) !important;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -12,8 +12,12 @@
|
|||
@import "./ui-operations.scss";
|
||||
@import 'react-loading-skeleton/dist/skeleton.css';
|
||||
@import './table-component.scss';
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
@import "./componentdesign.scss";
|
||||
@import './pages-sidebar.scss';
|
||||
|
||||
@import "./componentdesign.scss";
|
||||
/* ibm-plex-sans-100 - latin */
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
|
|
@ -469,10 +473,10 @@ button {
|
|||
left: 0;
|
||||
overflow-x: hidden;
|
||||
flex: 1 1 auto;
|
||||
background-color: var(--base) !important;
|
||||
background-clip: border-box;
|
||||
margin-top: 48px;
|
||||
padding-top: 8px;
|
||||
background: var(--base) !important;
|
||||
|
||||
.accordion-item {
|
||||
border: solid var(--slate5);
|
||||
|
|
@ -4634,13 +4638,12 @@ input[type="text"] {
|
|||
}
|
||||
|
||||
.modal-footer-btn {
|
||||
justify-content: end;
|
||||
|
||||
button {
|
||||
& > *:nth-child(2) {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-modal-component-editor.dark {
|
||||
|
||||
|
|
@ -5730,7 +5733,7 @@ div#driver-page-overlay {
|
|||
.node-key {
|
||||
font-weight: 400 !important;
|
||||
margin-left: -0.25rem !important;
|
||||
justify-content: start !important;
|
||||
justify-content: flex-start !important;
|
||||
min-width: fit-content !important;
|
||||
}
|
||||
|
||||
|
|
@ -5843,7 +5846,7 @@ div#driver-page-overlay {
|
|||
border-bottom: 1px solid #eee;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
justify-content: flex-end
|
||||
}
|
||||
|
||||
.autosave-indicator {
|
||||
|
|
@ -6311,7 +6314,7 @@ a.step-item-disabled {
|
|||
}
|
||||
|
||||
.spinner {
|
||||
min-height: 220px;
|
||||
min-height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -7540,6 +7543,7 @@ tbody {
|
|||
|
||||
.workspace-content-wrapper,
|
||||
.database-page-content-wrap {
|
||||
background-color: var(--page-default);
|
||||
height: calc(100vh - 64px) !important;
|
||||
}
|
||||
|
||||
|
|
@ -7551,7 +7555,7 @@ tbody {
|
|||
.organization-page-sidebar {
|
||||
height: calc(100vh - 64px);
|
||||
max-width: 288px;
|
||||
background-color: var(--base);
|
||||
background-color: var(--page-default);
|
||||
border-right: 1px solid var(--slate5) !important;
|
||||
display: grid !important;
|
||||
grid-template-rows: auto 1fr auto !important;
|
||||
|
|
@ -7560,7 +7564,7 @@ tbody {
|
|||
.marketplace-page-sidebar {
|
||||
height: calc(100vh - 64px);
|
||||
max-width: 288px;
|
||||
background-color: var(--base);
|
||||
background-color: var(--page-default);
|
||||
border-right: 1px solid var(--slate5) !important;
|
||||
display: grid !important;
|
||||
grid-template-rows: auto 1fr auto !important;
|
||||
|
|
@ -7568,7 +7572,7 @@ tbody {
|
|||
|
||||
.home-page-sidebar {
|
||||
max-width: 288px;
|
||||
background-color: var(--base);
|
||||
background-color: var(--page-default);
|
||||
border-right: 1px solid var(--slate5);
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
|
|
@ -7593,7 +7597,7 @@ tbody {
|
|||
|
||||
.tooljet-database-sidebar {
|
||||
max-width: 288px;
|
||||
background: var(--base);
|
||||
background: var(--page-default);
|
||||
border-right: 1px solid var(--slate5);
|
||||
height: calc(100vh - 64px) !important;
|
||||
|
||||
|
|
@ -7987,6 +7991,7 @@ tbody {
|
|||
|
||||
|
||||
.tj-dashboard-section-header {
|
||||
background-color: var(--page-default);
|
||||
max-width: 288px;
|
||||
max-height: 64px;
|
||||
padding-top: 20px;
|
||||
|
|
@ -8053,6 +8058,7 @@ tbody {
|
|||
border-radius: 6px;
|
||||
|
||||
.react-select__control {
|
||||
background-color: var(--page-default);
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -8114,7 +8120,7 @@ tbody {
|
|||
|
||||
.home-page-footer {
|
||||
height: 52px;
|
||||
background-color: var(--base) !important;
|
||||
background-color: var(--page-default) !important;
|
||||
border-top: 1px solid var(--slate5) !important;
|
||||
width: calc(100% - 336px) !important;
|
||||
|
||||
|
|
@ -8148,6 +8154,61 @@ tbody {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.form-control-pagination {
|
||||
padding: 0 4px;
|
||||
width: fit-content;
|
||||
width: 30px !important;
|
||||
height: 20px !important;
|
||||
text-align: center;
|
||||
margin-bottom: 0px;
|
||||
gap: 16px !important;
|
||||
background: var(--base) !important;
|
||||
border: 1px solid var(--slate7) !important;
|
||||
border-radius: 6px;
|
||||
color: var(--slate12) !important;
|
||||
transition: none;
|
||||
padding-left: 0.4375rem;
|
||||
padding-right: 0.4375rem;
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
|
||||
|
||||
&:hover {
|
||||
background: var(--slate1) !important;
|
||||
border: 1px solid var(--slate8) !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
background: var(--indigo2) !important;
|
||||
border: 1px solid var(--indigo9) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&.input-error-border {
|
||||
border-color: #DB4324 !important;
|
||||
}
|
||||
|
||||
&:-webkit-autofill {
|
||||
box-shadow: 0 0 0 1000px var(--base) inset !important;
|
||||
-webkit-text-fill-color: var(--slate12) !important;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1000px var(--slate1) inset !important;
|
||||
-webkit-text-fill-color: var(--slate12) !important;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
box-shadow: 0 0 0 1000px var(--indigo2) inset !important;
|
||||
-webkit-text-fill-color: var(--slate12) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.unstyled-button {
|
||||
height: unset;
|
||||
|
|
@ -8248,6 +8309,7 @@ tbody {
|
|||
}
|
||||
|
||||
.home-page-content {
|
||||
background-color: var(--page-default);
|
||||
height: calc(100vh - 64px) !important;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
|
|
@ -8306,19 +8368,19 @@ tbody {
|
|||
|
||||
.toojet-db-table-footer {
|
||||
height: 52px;
|
||||
background: var(--base) !important;
|
||||
background: var(--page-default) !important;
|
||||
width: calc(100vw - 336px);
|
||||
}
|
||||
|
||||
.toojet-db-table-footer-collapse {
|
||||
height: 52px;
|
||||
background: var(--base) !important;
|
||||
background: var(--page-default) !important;
|
||||
width: calc(100vw - 48px);
|
||||
}
|
||||
|
||||
.toojet-db-table-footer-collapse {
|
||||
height: 52px;
|
||||
background: var(--base) !important;
|
||||
background: var(--page-default) !important;
|
||||
width: calc(100vw - 48px);
|
||||
}
|
||||
|
||||
|
|
@ -8783,6 +8845,7 @@ tbody {
|
|||
}
|
||||
|
||||
.tj-dashboard-header-wrap {
|
||||
background-color: var(--page-default);
|
||||
padding-top: 22px;
|
||||
padding-bottom: 22px;
|
||||
padding-left: 40px;
|
||||
|
|
@ -10042,7 +10105,8 @@ tbody {
|
|||
border-top: 1px solid var(--slate5) !important;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: end;
|
||||
|
||||
justify-content: flex-end;
|
||||
|
||||
.invite-btn {
|
||||
width: 140px;
|
||||
|
|
@ -10348,7 +10412,7 @@ tbody {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
justify-content: end;
|
||||
justify-content: flex-end
|
||||
}
|
||||
|
||||
.add-users-button {
|
||||
|
|
@ -11165,7 +11229,7 @@ tbody {
|
|||
|
||||
|
||||
.input-with-icon {
|
||||
justify-content: end;
|
||||
justify-content: flex-end
|
||||
}
|
||||
|
||||
.form-check-input {
|
||||
|
|
@ -11372,7 +11436,7 @@ tbody {
|
|||
}
|
||||
|
||||
.profile-page-content-wrap {
|
||||
background-color: var(--slate2);
|
||||
background-color: var(--page-default);
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
|
|
@ -11622,6 +11686,7 @@ tbody {
|
|||
.marketplace-body {
|
||||
height: calc(100vh - 64px) !important;
|
||||
overflow-y: auto;
|
||||
background-color: var(--page-default);
|
||||
}
|
||||
|
||||
.plugins-card {
|
||||
|
|
@ -12341,6 +12406,7 @@ tbody {
|
|||
.header>.navbar {
|
||||
background-color: var(--base) !important;
|
||||
border-bottom: 1px solid var(--slate5);
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12831,6 +12897,7 @@ tbody {
|
|||
}
|
||||
|
||||
.workspace-constants-wrapper {
|
||||
background-color: var(--page-default);
|
||||
height: calc(100vh - 64px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -13099,6 +13166,17 @@ tbody {
|
|||
}
|
||||
}
|
||||
|
||||
.component-spinner {
|
||||
animation: l13 1s infinite linear;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@keyframes l13 {
|
||||
100% {
|
||||
transform: rotate(1turn)
|
||||
}
|
||||
}
|
||||
|
||||
.widget-version-identifier {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
|
|
|
|||
|
|
@ -12,11 +12,20 @@ const Card = ({
|
|||
className,
|
||||
titleClassName,
|
||||
actionButton,
|
||||
darkMode,
|
||||
}) => {
|
||||
const DisplayIcon = ({ src }) => {
|
||||
if (typeof src !== 'string') return;
|
||||
|
||||
if (usePluginIcon) {
|
||||
//Fetch darkMode svgs
|
||||
|
||||
if (darkMode) {
|
||||
const darkSrc = `${src}Dark`;
|
||||
if (allSvgs[darkSrc]) {
|
||||
src = darkSrc;
|
||||
}
|
||||
}
|
||||
const Icon = allSvgs[src];
|
||||
return <Icon style={{ height, width }} className="card-icon" />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { getPrivateRoute } from '@/_helpers/routes';
|
|||
import { ConfirmDialog } from '@/_components';
|
||||
import useGlobalDatasourceUnsavedChanges from '@/_hooks/useGlobalDatasourceUnsavedChanges';
|
||||
import Settings from '@/_components/Settings';
|
||||
import { retrieveWhiteLabelLogo, fetchWhiteLabelDetails } from '@white-label/whiteLabelling';
|
||||
|
||||
function Layout({
|
||||
children,
|
||||
|
|
@ -25,6 +26,8 @@ function Layout({
|
|||
const currentUserValue = authenticationService.currentSessionValue;
|
||||
const admin = currentUserValue?.admin;
|
||||
const marketplaceEnabled = admin && window.public_config?.ENABLE_MARKETPLACE_FEATURE == 'true';
|
||||
fetchWhiteLabelDetails();
|
||||
const whiteLabelLogo = retrieveWhiteLabelLogo();
|
||||
|
||||
const {
|
||||
checkForUnsavedChanges,
|
||||
|
|
@ -60,7 +63,7 @@ function Layout({
|
|||
to={getPrivateRoute('dashboard')}
|
||||
onClick={(event) => checkForUnsavedChanges(getPrivateRoute('dashboard'), event)}
|
||||
>
|
||||
<Logo />
|
||||
{whiteLabelLogo ? <img src={whiteLabelLogo} /> : <Logo />}
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -43,11 +43,11 @@ const Pagination = ({
|
|||
<Button.Content iconSrc={'assets/images/icons/chevron-left.svg'} />
|
||||
</Button.UnstyledButton>
|
||||
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="d-flex align-items-center mx-1">
|
||||
<input
|
||||
disabled={isDisabled || disableInput}
|
||||
type="text"
|
||||
className="form-control mx-1"
|
||||
className="form-control-pagination"
|
||||
data-cy={`current-page-number-${currentPageNumber}`}
|
||||
value={currentPageNumber}
|
||||
onKeyDown={(event) => {
|
||||
|
|
|
|||
207
frontend/src/components/ui/Button/Button.jsx
Normal file
207
frontend/src/components/ui/Button/Button.jsx
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
import React, { forwardRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { cn } from '@/lib/utils';
|
||||
import Loader from '@/components/ui/utilComponents/loader';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { cva } from 'class-variance-authority';
|
||||
import './Button.scss';
|
||||
import { getDefaultIconFillColor, defaultButtonFillColour, getIconSize } from './buttonUtils.js';
|
||||
|
||||
const buttonVariants = cva('tw-flex tw-justify-center tw-items-center tw-font-medium', {
|
||||
variants: {
|
||||
variant: {
|
||||
primary: `
|
||||
tw-text-text-on-solid tw-bg-button-primary hover:tw-bg-button-primary-hover
|
||||
active:tw-bg-button-primary-pressed active:tw-border-border-accent-strong
|
||||
disabled:tw-bg-button-primary-disabled tw-border-none
|
||||
tw-interactice-focus tw-focus-visible:tw-outline-none`,
|
||||
secondary: `
|
||||
tw-text-text-default tw-border tw-border-solid tw-border-border-accent-weak
|
||||
tw-bg-button-secondary hover:tw-border-border-accent-strong
|
||||
hover:tw-bg-button-secondary-hover active:tw-bg-button-secondary-pressed
|
||||
active:tw-border-border-accent-strong
|
||||
disabled:tw-border-border-default
|
||||
disabled:tw-bg-button-secondary-disabled disabled:tw-text-text-disabled
|
||||
tw-focus-visible:tw-border-border-accent-weak
|
||||
tw-interactive-focus-nonsolid tw-focus-visible:tw-outline-none`,
|
||||
outline: `
|
||||
tw-text-text-default tw-border tw-border-solid tw-border-border-default
|
||||
tw-bg-button-secondary hover:tw-border-border-default
|
||||
hover:tw-bg-button-outline-hover active:tw-bg-button-outline-pressed
|
||||
active:tw-border-border-strong
|
||||
disabled:tw-border-border-default
|
||||
disabled:tw-bg-button-outline-disabled disabled:tw-text-text-disabled
|
||||
tw-focus-visible:tw-border-border-default
|
||||
tw-interactive-focus-nonsolid tw-focus-visible:tw-interactive-focus-outline`,
|
||||
ghost: `
|
||||
tw-border-none tw-text-text-default tw-bg-[#ffffff00] hover:tw-bg-button-outline-hover
|
||||
active:tw-bg-button-outline-pressed tw-focus-visible:tw-bg-button-outline
|
||||
tw-interactive-focus-nonsolid tw-disabled:tw-text-text-disabled tw-focus-visible:tw-interactive-focus-outline tw-border-none`,
|
||||
ghostBrand: `
|
||||
tw-border-none tw-text-text-accent tw-bg-[#ffffff00] hover:tw-bg-button-secondary-hover
|
||||
active:tw-bg-button-secondary-pressed tw-focus-visible:tw-bg-button-outline
|
||||
tw-disabled:tw-text-text-disabled tw-focus-visible:tw-interactive-focus-outline tw-border-none tw-interactive-focus-nonsolid`,
|
||||
dangerPrimary: `
|
||||
tw-text-text-on-solid tw-bg-button-danger-primary hover:tw-bg-button-danger-primary-hover
|
||||
active:tw-bg-button-danger-primary-pressed disabled:tw-bg-button-danger-primary-disabled
|
||||
tw-border-none tw-interactice-focus tw-focus-visible:tw-outline-none`,
|
||||
dangerSecondary: `
|
||||
tw-text-text-default tw-border tw-border-solid tw-border-border-danger-weak
|
||||
tw-bg-button-secondary hover:tw-border-border-danger-strong
|
||||
hover:tw-bg-button-danger-secondary-hover
|
||||
active:tw-border-border-danger-strong active:tw-bg-button-danger-secondary-pressed
|
||||
tw-disabled:tw-text-text-disabled tw-disabled:tw-border-border-default
|
||||
tw-disabled:tw-bg-button-danger-secondary-disabled
|
||||
tw-focus-visible:tw-border-border-danger-weak tw-focus-visible:tw-interactive-focus-outline tw-interactive-focus-nonsolid`,
|
||||
dangerGhost: `
|
||||
tw-border-none tw-bg-[#ffffff00] tw-text-text-default hover:tw-bg-button-danger-secondary-hover
|
||||
active:tw-bg-button-danger-secondary-pressed tw-disabled:tw-border-border-default
|
||||
tw-disabled:tw-bg-button-danger-secondary-disabled
|
||||
tw-disabled:tw-text-text-disabled tw-focus-visible:tw-interactive-focus-outline tw-interactive-focus-nonsolid`,
|
||||
},
|
||||
size: {
|
||||
large: `tw-h-[40px] tw-gap-[8px] tw-py-[10px] tw-rounded-[10px] tw-text-lg`,
|
||||
default: `tw-h-[32px] tw-gap-[6px] tw-py-7px] tw-rounded-[8px] tw-text-base`,
|
||||
medium: `tw-h-[28px] tw-gap-[6px] tw-py-[5px] tw-rounded-[6px] tw-text-base`,
|
||||
small: `tw-h-[20px] tw-gap-[4px] tw-py-[2px] tw-rounded-[4px] tw-text-sm`,
|
||||
},
|
||||
},
|
||||
//this part will be updated/improved
|
||||
compoundVariants: [
|
||||
{
|
||||
iconOnly: true,
|
||||
size: 'large',
|
||||
className: 'tw-w-[40px] tw-px-[10px]',
|
||||
},
|
||||
{
|
||||
iconOnly: true,
|
||||
size: 'default',
|
||||
className: 'tw-w-[32px] tw-px-[7px]',
|
||||
},
|
||||
{
|
||||
iconOnly: true,
|
||||
size: 'medium',
|
||||
className: 'tw-w-[28px] tw-px-[5px]',
|
||||
},
|
||||
{
|
||||
iconOnly: true,
|
||||
size: 'small',
|
||||
className: 'tw-w-[20px] tw-px-[2px]',
|
||||
},
|
||||
{
|
||||
iconOnly: false,
|
||||
size: 'large',
|
||||
className: 'tw-px-[20px]',
|
||||
},
|
||||
{
|
||||
iconOnly: false,
|
||||
size: 'default',
|
||||
className: 'tw-px-[12px]',
|
||||
},
|
||||
{
|
||||
iconOnly: false,
|
||||
size: 'medium',
|
||||
className: 'tw-px-[10px]',
|
||||
},
|
||||
{
|
||||
iconOnly: false,
|
||||
size: 'small',
|
||||
className: 'tw-px-[8px]',
|
||||
},
|
||||
],
|
||||
defaultVariants: {
|
||||
variant: 'primary',
|
||||
size: 'default',
|
||||
},
|
||||
});
|
||||
|
||||
const Button = forwardRef(
|
||||
(
|
||||
{
|
||||
className,
|
||||
variant = 'primary',
|
||||
size = 'default',
|
||||
leadingIcon,
|
||||
trailingIcon,
|
||||
isLoading,
|
||||
disabled,
|
||||
asChild = false,
|
||||
fill = '',
|
||||
iconOnly = false, // as normal button and icon have diff styles make sure to pass it as truw when icon only button is used
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const iconFillColor = !defaultButtonFillColour.includes(fill) && fill ? fill : getDefaultIconFillColor(variant);
|
||||
const Comp = asChild ? Slot : 'button';
|
||||
const leadingIconElement = leadingIcon && (
|
||||
<SolidIcon name={leadingIcon} height={getIconSize(size)} width={getIconSize(size)} fill={iconFillColor} />
|
||||
);
|
||||
const trailingIconElement = trailingIcon && (
|
||||
<SolidIcon name={trailingIcon} height={getIconSize(size)} width={getIconSize(size)} fill={iconFillColor} />
|
||||
);
|
||||
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, iconOnly, className }))}
|
||||
ref={ref}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="tw-flex tw-justify-center tw-items-center">
|
||||
<Loader color={iconFillColor} width={getIconSize(size)} />
|
||||
<a className="tw-invisible">{props.children}</a>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{leadingIconElement}
|
||||
{props.children}
|
||||
{trailingIconElement}
|
||||
</>
|
||||
)}
|
||||
</Comp>
|
||||
);
|
||||
}
|
||||
);
|
||||
Button.displayName = 'Button'; //debugging purposes and helpful in React Developer Tools
|
||||
|
||||
Button.propTypes = {
|
||||
className: PropTypes.string,
|
||||
variant: PropTypes.oneOf([
|
||||
'primary',
|
||||
'secondary',
|
||||
'outline',
|
||||
'ghost',
|
||||
'ghostBrand',
|
||||
'dangerPrimary',
|
||||
'dangerSecondary',
|
||||
'dangerGhost',
|
||||
]),
|
||||
size: PropTypes.oneOf(['large', 'default', 'medium', 'small']),
|
||||
isLoading: PropTypes.bool,
|
||||
iconOnly: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
asChild: PropTypes.bool,
|
||||
fill: PropTypes.string,
|
||||
leadingIcon: PropTypes.string,
|
||||
trailingIcon: PropTypes.string,
|
||||
};
|
||||
|
||||
Button.defaultProps = {
|
||||
className: '',
|
||||
variant: 'primary',
|
||||
size: 'default',
|
||||
isLoading: false,
|
||||
disabled: false,
|
||||
asChild: false,
|
||||
iconOnly: false,
|
||||
fill: '',
|
||||
leadingIcon: '',
|
||||
trailingIcon: '',
|
||||
};
|
||||
|
||||
export { Button, buttonVariants };
|
||||
16
frontend/src/components/ui/Button/Button.scss
Normal file
16
frontend/src/components/ui/Button/Button.scss
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
.interactice-focus {
|
||||
&:focus-visible {
|
||||
box-shadow: 0px 0px 0px 2px var(--interactive-focus-outline, #4368E3), 0px 0px 0px 2px var(--interactive-focus-inner-shadow, #FFF) inset;
|
||||
}
|
||||
}
|
||||
|
||||
.interactive-focus-nonsolid {
|
||||
&:focus-visible {
|
||||
box-shadow: 0px 0px 0px 2px var(--interactive-focus-outline, #4368E3);
|
||||
outline-offset: 2px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.button-content {
|
||||
min-width: 100px;
|
||||
}
|
||||
104
frontend/src/components/ui/Button/button.stories.jsx
Normal file
104
frontend/src/components/ui/Button/button.stories.jsx
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { Button } from './Button';
|
||||
import * as React from 'react';
|
||||
|
||||
// Function to determine default icon fill color
|
||||
const getDefaultIconFillColor = (variant, customFill = '') => {
|
||||
if (customFill) {
|
||||
return customFill;
|
||||
}
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
case 'dangerPrimary':
|
||||
return 'var(--icon-on-solid)';
|
||||
case 'secondary':
|
||||
case 'ghostBrand':
|
||||
return 'var(--icon-brand)';
|
||||
case 'outline':
|
||||
case 'ghost':
|
||||
return 'var(--icon-strong)';
|
||||
case 'dangerSecondary':
|
||||
case 'dangerGhost':
|
||||
return 'var(--icon-danger)';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// Storybook configuration
|
||||
export default {
|
||||
title: 'Components/Button',
|
||||
component: Button,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
argTypes: {
|
||||
onClick: { action: 'Clicked' },
|
||||
variant: {
|
||||
control: {
|
||||
type: 'select',
|
||||
options: [
|
||||
'primary',
|
||||
'secondary',
|
||||
'outline',
|
||||
'ghost',
|
||||
'ghostBrand',
|
||||
'dangerPrimary',
|
||||
'dangerSecondary',
|
||||
'dangerGhost',
|
||||
],
|
||||
},
|
||||
},
|
||||
fill: { control: 'color' },
|
||||
},
|
||||
};
|
||||
|
||||
// Button template
|
||||
const Template = (args) => <Button {...args} />;
|
||||
|
||||
// Primary button story
|
||||
export const RocketButton = Template.bind({});
|
||||
RocketButton.args = {
|
||||
variant: 'primary',
|
||||
children: 'Button',
|
||||
size: 'default',
|
||||
iconOnly: false,
|
||||
};
|
||||
|
||||
// Button with leading icon story
|
||||
export const RocketButtonWithIcon = (args) => {
|
||||
const variant = args.variant || 'primary';
|
||||
const fill = ''; // If fill is provided by user, it will be used; otherwise, it falls back to defaults
|
||||
const color = getDefaultIconFillColor(variant, fill);
|
||||
|
||||
return <Button {...args} fill={color} iconOnly={false} leadingIcon="smilerectangle" />;
|
||||
};
|
||||
RocketButtonWithIcon.args = {
|
||||
...RocketButton.args,
|
||||
};
|
||||
|
||||
// Button with trailing icon story
|
||||
export const RocketButtonWithTrailingIcon = (args) => {
|
||||
const variant = args.variant || 'primary';
|
||||
const fill = '';
|
||||
const color = getDefaultIconFillColor(variant, fill);
|
||||
|
||||
return <Button {...args} fill={color} iconOnly={false} trailingIcon="smilerectangle" />;
|
||||
};
|
||||
RocketButtonWithTrailingIcon.args = {
|
||||
...RocketButton.args,
|
||||
};
|
||||
|
||||
// Button with icon only story
|
||||
export const Icon = (args) => {
|
||||
const variant = args.variant || 'primary';
|
||||
const fill = '';
|
||||
const color = getDefaultIconFillColor(variant, fill);
|
||||
|
||||
return <Button {...args} fill={color} trailingIcon="smilerectangle" />;
|
||||
};
|
||||
Icon.args = {
|
||||
...RocketButton.args,
|
||||
children: null,
|
||||
iconOnly: true,
|
||||
};
|
||||
32
frontend/src/components/ui/Button/buttonUtils.js
Normal file
32
frontend/src/components/ui/Button/buttonUtils.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
export const getDefaultIconFillColor = (variant) => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
case 'dangerPrimary':
|
||||
return 'var(--icon-on-solid)';
|
||||
case 'secondary':
|
||||
case 'ghostBrand':
|
||||
return 'var(--icon-brand)';
|
||||
case 'outline':
|
||||
case 'ghost':
|
||||
return 'var(--icon-strong)';
|
||||
case 'dangerSecondary':
|
||||
case 'dangerGhost':
|
||||
return 'var(--icon-danger)';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
export const defaultButtonFillColour = ['#FFFFFF', '#4368E3', '#ACB2B9', '#D72D39']; // all default fill colors
|
||||
|
||||
export const getIconSize = (size) => {
|
||||
switch (size) {
|
||||
case 'large':
|
||||
return '20px';
|
||||
case 'default':
|
||||
return '16px';
|
||||
case 'medium':
|
||||
return '14px';
|
||||
case 'small':
|
||||
return '12px';
|
||||
}
|
||||
};
|
||||
19
frontend/src/components/ui/utilComponents/loader.jsx
Normal file
19
frontend/src/components/ui/utilComponents/loader.jsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
|
||||
const Loader = ({ color, width }) => {
|
||||
const loaderStyle = {
|
||||
width,
|
||||
height: width,
|
||||
aspectRatio: '1',
|
||||
borderRadius: '50%',
|
||||
background: `radial-gradient(farthest-side, ${
|
||||
color || '#FFFFFF'
|
||||
} 90%, transparent 94%) top/4px 4px no-repeat, conic-gradient(#0000 30%, ${color || '#FFFFFF'})`,
|
||||
WebkitMask: `radial-gradient(farthest-side, transparent calc(100% - 3px), #000 0)`,
|
||||
animation: 'l13 1s infinite linear',
|
||||
};
|
||||
|
||||
return <div className="component-spinner" style={loaderStyle}></div>;
|
||||
};
|
||||
|
||||
export default Loader;
|
||||
6
frontend/src/lib/utils.js
Normal file
6
frontend/src/lib/utils.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import cx from 'classnames';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs) {
|
||||
return twMerge(cx(inputs));
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Button } from './Button';
|
||||
import './header.scss';
|
||||
|
||||
export const Header = ({ user, onLogin, onLogout, onCreateAccount }) => (
|
||||
<header>
|
||||
<div className="storybook-header">
|
||||
<div>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z" fill="#FFF" />
|
||||
<path d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z" fill="#555AB9" />
|
||||
<path d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z" fill="#91BAF8" />
|
||||
</g>
|
||||
</svg>
|
||||
<h1>Acme</h1>
|
||||
</div>
|
||||
<div>
|
||||
{user ? (
|
||||
<>
|
||||
<span className="welcome">
|
||||
Welcome, <b>{user.name}</b>!
|
||||
</span>
|
||||
<Button size="small" onClick={onLogout} label="Log out" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button size="small" onClick={onLogin} label="Log in" />
|
||||
<Button primary size="small" onClick={onCreateAccount} label="Sign up" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
Header.propTypes = {
|
||||
user: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
}),
|
||||
onLogin: PropTypes.func.isRequired,
|
||||
onLogout: PropTypes.func.isRequired,
|
||||
onCreateAccount: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
Header.defaultProps = {
|
||||
user: null,
|
||||
};
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
import { Header } from './Header';
|
||||
|
||||
export default {
|
||||
title: 'Example/Header',
|
||||
component: Header,
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
// More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
};
|
||||
|
||||
export const LoggedIn = {
|
||||
args: {
|
||||
user: {
|
||||
name: 'Jane Doe',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const LoggedOut = {};
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Header } from './Header';
|
||||
import './page.scss';
|
||||
|
||||
export const Page = () => {
|
||||
const [user, setUser] = React.useState();
|
||||
|
||||
return (
|
||||
<article>
|
||||
<Header
|
||||
user={user}
|
||||
onLogin={() => setUser({ name: 'Jane Doe' })}
|
||||
onLogout={() => setUser(undefined)}
|
||||
onCreateAccount={() => setUser({ name: 'Jane Doe' })}
|
||||
/>
|
||||
|
||||
<section className="storybook-page">
|
||||
<h2>Pages in Storybook</h2>
|
||||
<p>
|
||||
We recommend building UIs with a{' '}
|
||||
<a href="https://componentdriven.org" target="_blank" rel="noopener noreferrer">
|
||||
<strong>component-driven</strong>
|
||||
</a>{' '}
|
||||
process starting with atomic components and ending with pages.
|
||||
</p>
|
||||
<p>
|
||||
Render pages with mock data. This makes it easy to build and review page states without needing to navigate to
|
||||
them in your app. Here are some handy patterns for managing page data in Storybook:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Use a higher-level connected component. Storybook helps you compose such data from the of child component
|
||||
stories
|
||||
</li>
|
||||
<li>
|
||||
Assemble data in the page component from your services. You can mock these services out using Storybook.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Get a guided tutorial on component-driven development at{' '}
|
||||
<a href="https://storybook.js.org/tutorials/" target="_blank" rel="noopener noreferrer">
|
||||
Storybook tutorials
|
||||
</a>
|
||||
. Read more in the{' '}
|
||||
<a href="https://storybook.js.org/docs" target="_blank" rel="noopener noreferrer">
|
||||
docs
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<div className="tip-wrapper">
|
||||
<span className="tip">Tip</span> Adjust the width of the canvas with the{' '}
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
|
||||
id="a"
|
||||
fill="#999"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
Viewports addon in the toolbar
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import { within, userEvent } from '@storybook/testing-library';
|
||||
|
||||
import { Page } from './Page';
|
||||
|
||||
export default {
|
||||
title: 'Example/Page',
|
||||
component: Page,
|
||||
parameters: {
|
||||
// More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
};
|
||||
|
||||
export const LoggedOut = {};
|
||||
|
||||
// More on interaction testing: https://storybook.js.org/docs/react/writing-tests/interaction-testing
|
||||
export const LoggedIn = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const loginButton = await canvas.getByRole('button', {
|
||||
name: /Log in/i,
|
||||
});
|
||||
await userEvent.click(loginButton);
|
||||
},
|
||||
};
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
.storybook-header {
|
||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.storybook-header svg {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.storybook-header h1 {
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
margin: 6px 0 6px 10px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.storybook-header button + button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.storybook-header .welcome {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
.storybook-page {
|
||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
padding: 48px 20px;
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.storybook-page h2 {
|
||||
font-weight: 700;
|
||||
font-size: 32px;
|
||||
line-height: 1;
|
||||
margin: 0 0 4px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.storybook-page p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.storybook-page a {
|
||||
text-decoration: none;
|
||||
color: #1ea7fd;
|
||||
}
|
||||
|
||||
.storybook-page ul {
|
||||
padding-left: 30px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.storybook-page li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.storybook-page .tip {
|
||||
display: inline-block;
|
||||
border-radius: 1em;
|
||||
font-size: 11px;
|
||||
line-height: 12px;
|
||||
font-weight: 700;
|
||||
background: #e7fdd8;
|
||||
color: #66bf3c;
|
||||
padding: 4px 12px;
|
||||
margin-right: 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.storybook-page .tip-wrapper {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.storybook-page .tip-wrapper svg {
|
||||
display: inline-block;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
margin-right: 4px;
|
||||
vertical-align: top;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.storybook-page .tip-wrapper svg path {
|
||||
fill: #1ea7fd;
|
||||
}
|
||||
122
frontend/tailwind.config.js
Normal file
122
frontend/tailwind.config.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: ['class'],
|
||||
content: ['./pages/**/*.{js,jsx}', './components/**/*.{js,jsx}', './app/**/*.{js,jsx}', './src/**/*.{js,jsx}'],
|
||||
prefix: 'tw-',
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'page-default': 'var(--page-default)',
|
||||
'page-weak': 'var(--page-weak)',
|
||||
'background-surface-layer-01': 'var(--background-surface-layer-01)',
|
||||
'background-surface-layer-02': 'var(--background-surface-layer-02)',
|
||||
'background-surface-layer-03': 'var(--background-surface-layer-03)',
|
||||
'background-accent-strong': 'var(--background-accent-strong)',
|
||||
'background-accent-weak': 'var(--background-accent-weak)',
|
||||
'background-success-strong': 'var(--background-success-strong)',
|
||||
'background-success-weak': 'var(--background-success-weak)',
|
||||
'background-error-strong': 'var(--background-error-strong)',
|
||||
'background-error-weak': 'var(--background-error-weak)',
|
||||
'background-warning-stong': 'var(--background-warning-stong)',
|
||||
'background-warning-weak': 'var(--background-warning-weak)',
|
||||
'background-inverse': 'var(--background-Inverse)',
|
||||
'text-default': 'var(--text-default)',
|
||||
'text-medium': 'var(--text-medium)',
|
||||
'text-placeholder': 'var(--text-placeholder)',
|
||||
'text-inverse': 'var(--text-inverse)',
|
||||
'text-brand': 'var(--text-brand)',
|
||||
'text-accent': 'var(--text-brand)',
|
||||
'text-selected': 'var(--text-selected)',
|
||||
'text-success': 'var(--text-success)',
|
||||
'text-warning': 'var(--text-warning)',
|
||||
'text-danger': 'var(--text-danger)',
|
||||
'text-on-solid': 'var(--text-on-solid)',
|
||||
'text-disabled': 'var(--text-disabled)',
|
||||
'text-disabled-on-solid': 'var(--text-disabled-on-solid)',
|
||||
'icon-strong': 'var(--icon-strong)',
|
||||
'icon-default': 'var(--icon-default)',
|
||||
'icon-weak': 'var(--icon-weak)',
|
||||
'icon-inverse': 'var(--icon-inverse)',
|
||||
'icon-on-solid': 'var(--icon-on-solid)',
|
||||
'icon-accent': 'var(--icon-accent)',
|
||||
'icon-brand': 'var(--icon-brand)',
|
||||
'icon-success': 'var(--icon-success)',
|
||||
'icon-warning': 'var(--icon-warning)',
|
||||
'icon-danger': 'var(--icon-danger)',
|
||||
'icon-disabled': 'var(--icon-disabled)',
|
||||
'border-strong': 'var(--border-strong)',
|
||||
'border-default': 'var(--border-default)',
|
||||
'border-weak': 'var(--border-weak)',
|
||||
'border-accent-strong': 'var(--border-accent-strong)',
|
||||
'border-accent-weak': 'var(--border-accent-weak)',
|
||||
'border-success-strong': 'var(--border-success-strong)',
|
||||
'border-success-weak': 'var(--border-success-weak)',
|
||||
'border-warning-strong': 'var(--border-warning-strong)',
|
||||
'border-warning-weak': 'var(--border-warning-weak)',
|
||||
'border-danger-strong': 'var(--border-danger-strong)',
|
||||
'border-danger-weak': 'var(--border-danger-weak)',
|
||||
'border-disabled': 'var(--border-disabled)',
|
||||
'interactive-weak': 'var(--interactive-weak)',
|
||||
'interactive-default': 'var(--interactive-default)',
|
||||
'interactive-hover': 'var(--interactive-hover)',
|
||||
'interactive-selected': 'var(--interactive-selected)',
|
||||
'interactive-disabled': 'var(--interactive-disabled)',
|
||||
'interactive-focus-outline': 'var(--interactive-focus-outline)',
|
||||
'interactive-focus-inner-shadow': 'var(--interactive-focus-inner-shadow)',
|
||||
'button-primary': 'var(--button-primary)',
|
||||
'button-primary-hover': 'var(--button-primary-hover)',
|
||||
'button-primary-pressed': 'var(--button-primary-pressed)',
|
||||
'button-primary-disabled': 'var(--button-primary-disabled)',
|
||||
'button-secondary': 'var(--button-secondary)',
|
||||
'button-secondary-hover': 'var(--button-secondary-hover)',
|
||||
'button-secondary-pressed': 'var(--button-secondary-pressed)',
|
||||
'button-secondary-disabled': 'var(--button-secondary-disabled)',
|
||||
'button-outline': 'var(--button-outline)',
|
||||
'button-outline-hover': 'var(--button-outline-hover)',
|
||||
'button-outline-pressed': 'var(--button-outline-pressed)',
|
||||
'button-outline-disabled': 'var(--button-outline-disabled)',
|
||||
'button-danger-primary': 'var(--button-danger-primary)',
|
||||
'button-danger-primary-hover': 'var(--button-danger-primary-hover)',
|
||||
'button-danger-primary-pressed': 'var(--button-danger-primary-pressed)',
|
||||
'button-danger-primary-disabled': 'var(--button-danger-primary-disabled)',
|
||||
'button-danger-secondary': 'var(--button-danger-secondary)',
|
||||
'button-danger-secondary-hover': 'var(--button-danger-secondary-hover)',
|
||||
'button-danger-secondary-pressed': 'var(--button-danger-secondary-pressed)',
|
||||
'button-danger-secondary-disabled': 'var(--button-danger-secondary-disabled)',
|
||||
'switch-tab': 'var(--switch-tab)',
|
||||
'switch-tag': 'var(--switch-tag)',
|
||||
'switch-background-off': 'var(--switch-background-off)',
|
||||
'switch-background-on': 'var(--switch-background-on)',
|
||||
'slider-handle': 'var(--slider-handle)',
|
||||
'slider-track': 'var(--slider-track)',
|
||||
'slider-fill': 'var(--slider-fill)',
|
||||
},
|
||||
boxShadow: {
|
||||
'interactive-focus-outline': ' 0px 0px 0px 2px var(--interactive-focus-outline)',
|
||||
'interactive-focus-outline-inset': 'inset 0px 0px 0px 2px #fff',
|
||||
},
|
||||
fontSize: {
|
||||
sm: ['11px', '16px'],
|
||||
base: ['12px', '18px'],
|
||||
lg: ['14px', '20px'],
|
||||
xl: ['16px', '24px'],
|
||||
},
|
||||
fontWeight: {
|
||||
thin: '100',
|
||||
hairline: '100',
|
||||
extralight: '200',
|
||||
light: '300',
|
||||
normal: '400',
|
||||
medium: '500',
|
||||
semibold: '600',
|
||||
bold: '700',
|
||||
extrabold: '800',
|
||||
black: '900',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require('tailwindcss-animate')],
|
||||
};
|
||||
|
|
@ -87,6 +87,7 @@ module.exports = {
|
|||
'@': path.resolve(__dirname, 'src/'),
|
||||
'@ee': path.resolve(__dirname, 'ee/'),
|
||||
'@assets': path.resolve(__dirname, 'assets/'),
|
||||
'@white-label': path.resolve(__dirname, 'ce/white-label'),
|
||||
},
|
||||
},
|
||||
devtool: environment === 'development' ? 'eval-cheap-source-map' : 'hidden-source-map',
|
||||
|
|
@ -146,6 +147,9 @@ module.exports = {
|
|||
{
|
||||
loader: 'css-loader',
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -53,4 +53,4 @@
|
|||
"prepare": "husky install",
|
||||
"update-version": "node update-version.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
20
plugins/packages/grpc/lib/darkIcon.svg
Normal file
20
plugins/packages/grpc/lib/darkIcon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.3 KiB |
10
plugins/packages/influxdb/lib/darkIcon.svg
Normal file
10
plugins/packages/influxdb/lib/darkIcon.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_814_122599)">
|
||||
<path d="M5.31492 14.1198L5.3578 14.1306L15.4345 17.2148C15.5189 17.239 15.5977 17.2796 15.6663 17.3343C15.735 17.389 15.7922 17.4566 15.8346 17.5335C15.8771 17.6103 15.904 17.6948 15.9137 17.782C15.9215 17.8518 15.9182 17.9222 15.9041 17.9908L15.8915 18.0418L14.2029 23.4472C14.1435 23.6118 14.0274 23.7502 13.8755 23.8372C13.7374 23.9163 13.5777 23.9485 13.4205 23.9295L13.3734 23.9223L3.29674 20.8085C3.12842 20.7572 2.98715 20.6416 2.90369 20.4867C2.82783 20.3458 2.80533 20.183 2.8393 20.0278L2.85118 19.9814L4.52835 14.5875C4.57823 14.4173 4.69346 14.2737 4.84886 14.1881C4.9913 14.1096 5.15691 14.0857 5.31492 14.1198ZM22.326 16.588L22.3031 16.6117L15.5738 22.8417C15.3262 23.0893 15.1983 23.0075 15.2773 22.6851L15.2859 22.6521L16.6889 18.1378C16.7519 17.9512 16.8609 17.7834 17.0058 17.65C17.1347 17.5315 17.2883 17.4435 17.4551 17.3922L17.5183 17.3747L22.1431 16.3238C22.4709 16.206 22.5476 16.3372 22.347 16.5649L22.326 16.588ZM0.309343 9.55988L0.330828 9.58426L3.55262 13.0614C3.67909 13.2141 3.76791 13.3943 3.81197 13.5875C3.85112 13.7593 3.85393 13.9371 3.82049 14.1096L3.80626 14.174L2.4033 18.6884C2.31715 19.0263 2.16055 19.0375 2.0924 18.7221L2.08568 18.6884L0.0429213 9.74419C-0.0565851 9.40592 0.0964194 9.32653 0.309343 9.55988ZM20.8537 3.80734C20.9908 3.88939 21.0929 4.01818 21.1417 4.16921L21.1537 4.21093L23.5141 14.4482C23.539 14.5318 23.5462 14.6197 23.5352 14.7062C23.5242 14.7928 23.4954 14.8761 23.4506 14.9509C23.4057 15.0257 23.3456 15.0903 23.2743 15.1407C23.2172 15.1809 23.1541 15.2113 23.0873 15.2307L23.0366 15.2432L17.4864 16.5158C17.3173 16.5579 17.1384 16.5314 16.9889 16.4419C16.8519 16.3598 16.7498 16.231 16.7009 16.08L16.6889 16.0383L14.3354 5.80101C14.2931 5.63287 14.3191 5.45485 14.4078 5.30586C14.489 5.16929 14.6168 5.06731 14.7669 5.01807L14.8084 5.00597L20.3563 3.73344C20.5253 3.69126 20.7043 3.71782 20.8537 3.80734ZM13.6192 6.20097L13.6294 6.23508L15.8618 15.8989C15.9525 16.2504 15.7478 16.4394 15.4156 16.3642L15.3819 16.3558L5.8788 13.4316C5.54125 13.3389 5.46997 13.0695 5.69581 12.8228L5.72114 12.7964L12.9919 6.04317C13.2371 5.79803 13.5143 5.87952 13.6192 6.20097ZM8.39128 0.000197145C8.47781 0.00232254 8.56303 0.0216088 8.64203 0.0569367C8.70523 0.0851989 8.76345 0.123277 8.81455 0.169666L8.8515 0.205992L12.7039 4.37767C12.8204 4.50336 12.8823 4.66997 12.8763 4.84117C12.8708 4.99809 12.8086 5.14721 12.7021 5.26127L12.672 5.2915L4.95564 12.4469C4.82829 12.5645 4.65964 12.6271 4.48636 12.6211C4.32752 12.6156 4.17654 12.5529 4.06087 12.4453L4.03022 12.4149L0.170925 8.24092C0.0556386 8.11468 -0.00557994 7.9483 0.000400318 7.77747C0.00588223 7.62087 0.0674297 7.47196 0.173031 7.35743L0.202915 7.32707L7.92151 0.183146C7.98381 0.12307 8.05739 0.0759615 8.13804 0.0445557C8.2187 0.0131497 8.30476 -0.00192826 8.39128 0.000197145ZM10.3436 0.174841L10.3755 0.183182L19.1772 2.87673C19.5174 2.96727 19.5287 3.11973 19.2216 3.21647L19.1886 3.22627L14.5639 4.27488C14.3701 4.31463 14.1696 4.30737 13.9791 4.2537C13.8099 4.20601 13.6529 4.12289 13.5187 4.01014L13.4694 3.96648L10.2476 0.500741C9.9709 0.224092 10.0223 0.0953797 10.3436 0.174841Z" fill="#84E1E2"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_814_122599">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
3
plugins/packages/mariadb/lib/darkIcon.svg
Normal file
3
plugins/packages/mariadb/lib/darkIcon.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M23.8879 4.07409C23.8227 4.02037 23.7399 3.99268 23.6555 3.99636C23.4239 3.99636 23.1244 4.15346 22.9632 4.23774L22.8994 4.27047C22.629 4.40137 22.3347 4.47544 22.0345 4.48812C21.7269 4.49794 21.4618 4.51594 21.1165 4.55195C19.0708 4.76223 18.1585 6.32999 17.2797 7.8462C16.801 8.67099 16.3068 9.5277 15.6293 10.1823C15.4891 10.3182 15.3402 10.4448 15.1834 10.5611C14.4821 11.0824 13.6017 11.4547 12.9168 11.7181C12.2573 11.9702 11.5373 12.1968 10.8418 12.4161C10.2043 12.6166 9.60293 12.8064 9.0498 13.0118C8.80023 13.1042 8.5883 13.1754 8.40093 13.2376C7.89689 13.4013 7.53359 13.5265 7.00254 13.8922C6.79553 14.0338 6.58769 14.1868 6.44696 14.3013C6.02542 14.6377 5.65232 15.0306 5.33823 15.469C5.06814 15.8734 4.75601 16.248 4.40707 16.5867C4.29497 16.6963 4.09614 16.7504 3.7983 16.7504C3.44972 16.7504 3.02669 16.6783 2.57911 16.6022C2.11762 16.5204 1.64058 16.4427 1.23146 16.4427C0.899252 16.4427 0.644778 16.4967 0.454945 16.6063C0.454945 16.6063 0.13501 16.7929 0 17.0343L0.132556 17.094C0.338114 17.2055 0.528428 17.343 0.698782 17.5031C0.87652 17.6673 1.0745 17.8081 1.28792 17.9221C1.35605 17.9471 1.418 17.9864 1.46957 18.0375C1.41393 18.1193 1.3321 18.2248 1.24619 18.3377C0.77406 18.9555 0.498312 19.3458 0.656233 19.5586C0.731923 19.5979 0.816462 19.617 0.901707 19.6142C1.93106 19.6142 2.48419 19.3466 3.18379 19.0079C3.38672 18.9097 3.59292 18.8091 3.83839 18.7051C4.24751 18.5276 4.68773 18.2445 5.15495 17.945C5.76618 17.5465 6.40523 17.1374 7.02546 16.9402C7.53518 16.7847 8.06612 16.7102 8.59894 16.7193C9.25354 16.7193 9.9425 16.8068 10.6069 16.8919C11.102 16.9557 11.615 17.0212 12.1182 17.0515C12.3138 17.0629 12.4946 17.0686 12.6705 17.0686C12.9059 17.0693 13.1411 17.057 13.375 17.0318L13.4315 17.0122C13.7842 16.7954 13.9494 16.3298 14.1098 15.8797C14.2129 15.5901 14.2997 15.3299 14.4371 15.1646C14.4452 15.1565 14.454 15.1491 14.4633 15.1425C14.4698 15.1389 14.4772 15.1376 14.4845 15.1388C14.4918 15.14 14.4984 15.1436 14.5034 15.149C14.5034 15.149 14.5034 15.1531 14.5034 15.1621C14.4216 16.9222 13.713 18.0399 12.9962 19.0333L12.5175 19.5463C12.5175 19.5463 13.1877 19.5463 13.569 19.399C14.96 18.9833 16.0098 18.0669 16.774 16.6055C16.9625 16.2304 17.131 15.8455 17.2789 15.4526C17.292 15.4199 17.4123 15.3593 17.4008 15.5287C17.3967 15.5786 17.3934 15.6343 17.3894 15.6924C17.3894 15.7267 17.3894 15.7619 17.3828 15.7971C17.3632 16.0426 17.3051 16.5613 17.3051 16.5613L17.7347 16.3314C18.7706 15.6768 19.5692 14.3562 20.1747 12.3015C20.4267 11.4457 20.6116 10.5955 20.7744 9.84681C20.9692 8.94674 21.1369 8.17514 21.3292 7.87566C21.6311 7.40599 22.0918 7.08851 22.5378 6.78003C22.5983 6.7383 22.6597 6.6982 22.7194 6.65402C23.2799 6.26044 23.8371 5.80632 23.9599 4.95943V4.94061C24.0491 4.30893 23.9738 4.14773 23.8879 4.07409Z" fill="#B6795F"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
11
plugins/packages/mysql/lib/darkIcon.svg
Normal file
11
plugins/packages/mysql/lib/darkIcon.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_817_123574)">
|
||||
<path d="M22.0922 18.2073C20.7873 18.1748 19.776 18.3054 18.9279 18.6641C18.6832 18.7619 18.2917 18.762 18.2591 19.0719C18.3896 19.2025 18.4059 19.4145 18.5201 19.5938C18.7159 19.92 19.0583 20.3605 19.3683 20.5889C19.7108 20.8498 20.0533 21.1107 20.4121 21.3391C21.0483 21.7306 21.766 21.9589 22.3857 22.3504C22.7447 22.5786 23.1033 22.8723 23.4622 23.117C23.6417 23.2474 23.7558 23.4595 23.9842 23.541V23.4921C23.87 23.3453 23.8373 23.1333 23.7232 22.9702C23.5602 22.8071 23.397 22.6602 23.2339 22.4971C22.7609 21.8611 22.1737 21.3065 21.5376 20.8498C21.0157 20.4909 19.8739 20.0016 19.6618 19.3981C19.6618 19.3981 19.6455 19.3818 19.6292 19.3656C19.9881 19.3329 20.4121 19.2025 20.7547 19.1045C21.3093 18.9577 21.8149 18.9903 22.3857 18.8436C22.6467 18.7783 22.9077 18.6968 23.1686 18.6152V18.4685C22.8752 18.1748 22.663 17.7834 22.3531 17.5061C21.5213 16.7883 20.6078 16.0871 19.6618 15.4999C19.1561 15.1737 18.5037 14.9616 17.9655 14.6844C17.7699 14.5863 17.4435 14.5375 17.3294 14.3744C17.0358 14.0156 16.8727 13.5426 16.6606 13.1185C16.1878 12.2213 15.7309 11.2265 15.3232 10.2805C15.0297 9.64434 14.8502 9.00818 14.4914 8.42105C12.8114 5.64824 10.9846 3.96826 8.17911 2.32082C7.5756 1.97834 6.85799 1.83151 6.09137 1.65213C5.68371 1.63576 5.27584 1.60322 4.86807 1.58685C4.60714 1.47265 4.34611 1.16281 4.11772 1.01597C3.1882 0.428728 0.790405 -0.843382 0.105339 0.836597C-0.335069 1.89679 0.757766 2.94061 1.13289 3.47894C1.41029 3.85406 1.76904 4.2781 1.96479 4.70224C2.07899 4.97934 2.11152 5.27311 2.22572 5.56669C2.48675 6.28419 2.73131 7.08355 3.07389 7.75236C3.25337 8.09484 3.44902 8.4537 3.67741 8.76353C3.80787 8.94291 4.03616 9.02456 4.08517 9.31813C3.85689 9.64435 3.84052 10.1337 3.70995 10.5414C3.12281 12.3845 3.35119 14.668 4.183 16.0218C4.44393 16.4294 5.06381 17.3267 5.89562 16.9841C6.6296 16.6906 6.46649 15.7608 6.67851 14.9453C6.72752 14.7495 6.69488 14.6191 6.7927 14.4886C6.79279 14.5049 6.7927 14.5213 6.7927 14.5213C7.02109 14.9779 7.24938 15.4184 7.4614 15.875C7.96709 16.6742 8.84781 17.5061 9.58178 18.0607C9.97338 18.3542 10.2832 18.8599 10.7725 19.0393V18.9903H10.7399C10.642 18.8435 10.4952 18.7783 10.3648 18.6641C10.0712 18.3705 9.74489 18.0117 9.5166 17.6855C8.83164 16.772 8.22802 15.7608 7.68979 14.7169C7.42886 14.2113 7.20047 13.6567 6.98845 13.1511C6.89053 12.9553 6.89053 12.6618 6.72742 12.564C6.48276 12.9227 6.12401 13.2327 5.94453 13.6731C5.63479 14.3744 5.60205 15.2389 5.48785 16.136C5.42267 16.1523 5.45521 16.136 5.42257 16.1686C4.90071 16.038 4.72123 15.4999 4.52548 15.0431C4.03616 13.8851 3.95461 12.0257 4.37874 10.6883C4.49294 10.3457 4.98226 9.26922 4.78651 8.94301C4.68869 8.63307 4.36237 8.45369 4.183 8.20903C3.97098 7.89909 3.74259 7.50769 3.59585 7.16511C3.20436 6.25175 3.00861 5.24048 2.58458 4.32701C2.38883 3.90287 2.04634 3.46257 1.76904 3.07118C1.4592 2.63077 1.11662 2.32082 0.871953 1.79886C0.790503 1.61949 0.676205 1.32591 0.806669 1.13017C0.839312 0.999702 0.904492 0.95079 1.03506 0.918148C1.24708 0.738668 1.85059 0.967058 2.06261 1.06488C2.66612 1.30944 3.17171 1.53793 3.6774 1.88041C3.90569 2.04352 4.15035 2.35346 4.44392 2.43502H4.7865C5.30847 2.54911 5.89561 2.46766 6.38493 2.61439C7.24947 2.89159 8.03226 3.29946 8.73371 3.73987C10.8705 5.09363 12.6319 7.01837 13.8227 9.31814C14.0184 9.69316 14.0999 10.0358 14.2793 10.4272C14.6219 11.2265 15.046 12.042 15.3884 12.8249C15.7309 13.5914 16.0571 14.3744 16.5464 15.0106C16.7912 15.353 17.7697 15.5325 18.2101 15.7119C18.5364 15.8587 19.0421 15.9892 19.3356 16.1686C19.8901 16.511 20.4447 16.9026 20.9667 17.2777C21.2277 17.4734 22.0432 17.8812 22.0922 18.2073L22.0922 18.2073Z" fill="#E59639"/>
|
||||
<path d="M5.45483 4.03333C5.17753 4.03333 4.98188 4.06607 4.78613 4.11498C4.78613 4.1149 4.78613 4.13136 4.78613 4.14763H4.81878C4.94934 4.40855 5.17753 4.58803 5.34063 4.81632C5.4712 5.07725 5.58529 5.33828 5.71586 5.59931C5.73213 5.58294 5.7484 5.56667 5.7484 5.56667C5.97689 5.40346 6.09098 5.14253 6.09098 4.75114C5.99306 4.63694 5.97679 4.52275 5.89524 4.40855C5.79741 4.24534 5.58529 4.16389 5.45483 4.03333Z" fill="#E59639"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_817_123574">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
3
plugins/packages/zendesk/lib/darkIcon.svg
Normal file
3
plugins/packages/zendesk/lib/darkIcon.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.0857 7.70885V21.0928H0L11.0857 7.70885ZM11.0857 2.90601C11.0857 4.37606 10.5017 5.7859 9.46222 6.82539C8.42274 7.86487 7.01289 8.44885 5.54284 8.44885C4.07279 8.44885 2.66295 7.86487 1.62346 6.82539C0.583977 5.7859 0 4.37606 0 2.90601L11.0857 2.90601ZM12.9119 21.094C12.9119 19.6239 13.4959 18.2141 14.5354 17.1746C15.5749 16.1351 16.9847 15.5511 18.4548 15.5511C19.9248 15.5511 21.3347 16.1351 22.3742 17.1746C23.4137 18.2141 23.9976 19.6239 23.9976 21.094H12.9119ZM12.9119 16.2911V2.90601H24L12.9119 16.2899V16.2911Z" fill="#6DA78D"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 657 B |
|
|
@ -16,6 +16,7 @@ import {
|
|||
lifecycleEvents,
|
||||
URL_SSO_SOURCE,
|
||||
WORKSPACE_USER_STATUS,
|
||||
WORKSPACE_USER_SOURCE,
|
||||
} from 'src/helpers/user_lifecycle';
|
||||
import { dbTransactionWrap, generateInviteURL, generateNextNameAndSlug, isValidDomain } from 'src/helpers/utils.helper';
|
||||
import { DeepPartial, EntityManager } from 'typeorm';
|
||||
|
|
@ -74,7 +75,13 @@ export class OauthService {
|
|||
manager
|
||||
);
|
||||
// Setting up invited organization, organization user status should be invited if user status is invited
|
||||
await this.organizationUsersService.create(user, organization, !!user.invitationToken, manager);
|
||||
await this.organizationUsersService.create(
|
||||
user,
|
||||
organization,
|
||||
!!user.invitationToken,
|
||||
manager,
|
||||
WORKSPACE_USER_SOURCE.SIGNUP
|
||||
);
|
||||
|
||||
if (defaultOrganization) {
|
||||
// Setting up default organization
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
|
||||
|
||||
export class AddSourceToOrganizationUsers1716975639914 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.addColumns('organization_users', [
|
||||
new TableColumn({
|
||||
name: 'source',
|
||||
type: 'enum',
|
||||
enumName: 'source',
|
||||
enum: ['signup', 'invite'],
|
||||
default: `'invite'`,
|
||||
isNullable: false,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropColumns('organization_users', ['source']);
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ import { InvitedUser } from 'src/decorators/invited-user.decorator';
|
|||
import { InvitedUserSessionDto } from '@dto/invited-user-session.dto';
|
||||
import { ActivateAccountWithTokenDto } from '@dto/activate-account-with-token.dto';
|
||||
import { OrganizationInviteAuthGuard } from 'src/modules/auth/organization-invite-auth.guard';
|
||||
import { ResendInviteDto } from '@dto/resend-invite.dto';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
|
|
@ -162,8 +163,8 @@ export class AppController {
|
|||
@UseGuards(SignupDisableGuard)
|
||||
@UseGuards(FirstUserSignupDisableGuard)
|
||||
@Post('resend-invite')
|
||||
async resendInvite(@Body('email') email: string) {
|
||||
return this.authService.resendEmail(email);
|
||||
async resendInvite(@Body() body: ResendInviteDto) {
|
||||
return this.authService.resendEmail(body);
|
||||
}
|
||||
|
||||
@UseGuards(FirstUserSignupDisableGuard)
|
||||
|
|
@ -172,6 +173,11 @@ export class AppController {
|
|||
return await this.authService.verifyInviteToken(token, organizationToken);
|
||||
}
|
||||
|
||||
@Get('invitee-details')
|
||||
async getInviteeDetails(@Query('token') token) {
|
||||
return await this.authService.getInviteeDetails(token);
|
||||
}
|
||||
|
||||
@UseGuards(FirstUserSignupDisableGuard)
|
||||
@Get('verify-organization-token')
|
||||
async verifyOrganizationToken(@Query('token') token) {
|
||||
|
|
|
|||
18
server/src/dto/resend-invite.dto.ts
Normal file
18
server/src/dto/resend-invite.dto.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { Transform } from 'class-transformer';
|
||||
import { IsString, IsOptional, IsEmail, IsNotEmpty } from 'class-validator';
|
||||
import { lowercaseString } from 'src/helpers/utils.helper';
|
||||
|
||||
export class ResendInviteDto {
|
||||
@IsEmail()
|
||||
@Transform(({ value }) => lowercaseString(value))
|
||||
@IsNotEmpty()
|
||||
email: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
organizationId?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
redirectTo?: string;
|
||||
}
|
||||
|
|
@ -24,6 +24,9 @@ export class OrganizationUser extends BaseEntity {
|
|||
@Column({ type: 'enum', enumName: 'status', enum: ['invited', 'active', 'archived'] })
|
||||
status: string;
|
||||
|
||||
@Column({ type: 'enum', enumName: 'source', enum: ['signup', 'invite'] })
|
||||
source: string;
|
||||
|
||||
@Column({ name: 'organization_id' })
|
||||
organizationId: string;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ export enum SOURCE {
|
|||
WORKSPACE_SIGNUP = 'workspace_signup',
|
||||
}
|
||||
|
||||
export enum WORKSPACE_USER_SOURCE {
|
||||
INVITE = 'invite',
|
||||
SIGNUP = 'signup',
|
||||
}
|
||||
|
||||
export enum USER_STATUS {
|
||||
INVITED = 'invited',
|
||||
VERIFIED = 'verified',
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { OrganizationUsersService } from '@services/organization_users.service';
|
|||
import { OrganizationsService } from '@services/organizations.service';
|
||||
import { Organization } from 'src/entities/organization.entity';
|
||||
import { SSOConfigs } from 'src/entities/sso_config.entity';
|
||||
import { getUserErrorMessages, USER_STATUS } from 'src/helpers/user_lifecycle';
|
||||
import { getUserErrorMessages, USER_STATUS, WORKSPACE_USER_SOURCE } from 'src/helpers/user_lifecycle';
|
||||
|
||||
/*
|
||||
This guard will check all possible cases to reject an invalid invitation session request
|
||||
|
|
@ -106,11 +106,12 @@ export class InvitedUserSessionAuthGuard extends AuthGuard('jwt') {
|
|||
}
|
||||
|
||||
async onInvalidSession(invitedUser: any) {
|
||||
const { invitationToken, status } = invitedUser;
|
||||
const { invitationToken, status, organizationUserSource } = invitedUser;
|
||||
if (invitationToken && [USER_STATUS.INVITED, USER_STATUS.VERIFIED].includes(status as USER_STATUS)) {
|
||||
/* User doesn't have a valid session & User didn't activate account yet */
|
||||
return invitedUser;
|
||||
} else {
|
||||
if (organizationUserSource === WORKSPACE_USER_SOURCE.SIGNUP) return invitedUser;
|
||||
/* User doesn't have a session. Next?: login again and accept invite */
|
||||
const organization = await this.organizationService.fetchOrganization(invitedUser.invitedOrganizationId);
|
||||
const errorResponse = {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { OrganizationUsersService } from '@services/organization_users.service';
|
||||
import { WORKSPACE_USER_SOURCE } from 'src/helpers/user_lifecycle';
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationInviteAuthGuard extends AuthGuard('jwt') {
|
||||
constructor(private organizationUsersService: OrganizationUsersService) {
|
||||
super();
|
||||
}
|
||||
async canActivate(context: ExecutionContext): Promise<any> {
|
||||
let user;
|
||||
let user: any;
|
||||
const request = context.switchToHttp().getRequest();
|
||||
request.isInviteSession = true;
|
||||
request.isUserNotMandatory = true;
|
||||
|
||||
if (request?.cookies['tj_auth_token']) {
|
||||
try {
|
||||
user = await super.canActivate(context);
|
||||
|
|
@ -16,6 +22,12 @@ export class OrganizationInviteAuthGuard extends AuthGuard('jwt') {
|
|||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
const organizationUser = await this.organizationUsersService.getUser(request.body.token);
|
||||
if (organizationUser.source === WORKSPACE_USER_SOURCE.SIGNUP) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import {
|
|||
SOURCE,
|
||||
URL_SSO_SOURCE,
|
||||
WORKSPACE_USER_STATUS,
|
||||
WORKSPACE_USER_SOURCE,
|
||||
} from 'src/helpers/user_lifecycle';
|
||||
import { MetadataService } from './metadata.service';
|
||||
import { CookieOptions, Response } from 'express';
|
||||
|
|
@ -50,6 +51,7 @@ import { AppAuthenticationDto, AppSignupDto } from '@dto/app-authentication.dto'
|
|||
import { SIGNUP_ERRORS } from 'src/helpers/errors.constants';
|
||||
const bcrypt = require('bcrypt');
|
||||
const uuid = require('uuid');
|
||||
import { ResendInviteDto } from '@dto/resend-invite.dto';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
|
|
@ -65,7 +67,9 @@ export class AuthService {
|
|||
private emailService: EmailService,
|
||||
private metadataService: MetadataService,
|
||||
private configService: ConfigService,
|
||||
private sessionService: SessionService
|
||||
private sessionService: SessionService,
|
||||
@InjectRepository(Organization)
|
||||
private organizationsRepository: Repository<Organization>
|
||||
) {}
|
||||
|
||||
verifyToken(token: string) {
|
||||
|
|
@ -240,15 +244,72 @@ export class AuthService {
|
|||
});
|
||||
}
|
||||
|
||||
async resendEmail(email: string) {
|
||||
async resendEmail(body: ResendInviteDto) {
|
||||
const { email, organizationId, redirectTo } = body;
|
||||
if (!email) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
const existingUser = await this.usersService.findByEmail(email);
|
||||
if (existingUser?.organizationUsers?.some((ou) => ou.status === WORKSPACE_USER_STATUS.ACTIVE)) {
|
||||
|
||||
if (existingUser?.status === USER_STATUS.ARCHIVED) {
|
||||
throw new NotAcceptableException('User has been archived, please contact the administrator');
|
||||
}
|
||||
|
||||
if (!organizationId && existingUser?.organizationUsers?.some((ou) => ou.status === WORKSPACE_USER_STATUS.ACTIVE)) {
|
||||
throw new NotAcceptableException('Email already exists');
|
||||
}
|
||||
|
||||
let organizationUser: OrganizationUser;
|
||||
if (organizationId) {
|
||||
/* Workspace signup invitation email */
|
||||
organizationUser = existingUser.organizationUsers.find(
|
||||
(organizationUser) => organizationUser.organizationId === organizationId
|
||||
);
|
||||
if (organizationUser.status === WORKSPACE_USER_STATUS.ACTIVE) {
|
||||
throw new NotAcceptableException('User already exists in the workspace.');
|
||||
}
|
||||
if (organizationUser.status === WORKSPACE_USER_STATUS.ARCHIVED) {
|
||||
throw new NotAcceptableException('User has been archived, please contact the administrator');
|
||||
}
|
||||
}
|
||||
|
||||
if (organizationUser) {
|
||||
const invitedOrganization = await this.organizationsRepository.findOne({
|
||||
where: { id: organizationUser.organizationId },
|
||||
select: ['name', 'id'],
|
||||
});
|
||||
if (existingUser.invitationToken) {
|
||||
/* Not activated. */
|
||||
this.emailService
|
||||
.sendWelcomeEmail(
|
||||
existingUser.email,
|
||||
existingUser.firstName,
|
||||
existingUser.invitationToken,
|
||||
organizationUser.invitationToken,
|
||||
organizationUser.organizationId,
|
||||
invitedOrganization.name,
|
||||
null,
|
||||
redirectTo
|
||||
)
|
||||
.catch((err) => console.error('Error while sending welcome mail', err));
|
||||
return;
|
||||
} else {
|
||||
/* Already activated */
|
||||
this.emailService
|
||||
.sendOrganizationUserWelcomeEmail(
|
||||
existingUser.email,
|
||||
existingUser.firstName,
|
||||
null,
|
||||
organizationUser.invitationToken,
|
||||
invitedOrganization.name,
|
||||
organizationUser.organizationId,
|
||||
redirectTo
|
||||
)
|
||||
.catch((err) => console.error(err));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingUser?.invitationToken) {
|
||||
this.emailService
|
||||
.sendWelcomeEmail(existingUser.email, existingUser.firstName, existingUser.invitationToken)
|
||||
|
|
@ -351,7 +412,13 @@ export class AuthService {
|
|||
await this.organizationUsersService.create(user, personalWorkspace, true, manager);
|
||||
if (signingUpOrganization) {
|
||||
/* Attach the user and user groups to the organization */
|
||||
const organizationUser = await this.organizationUsersService.create(user, signingUpOrganization, true, manager);
|
||||
const organizationUser = await this.organizationUsersService.create(
|
||||
user,
|
||||
signingUpOrganization,
|
||||
true,
|
||||
manager,
|
||||
WORKSPACE_USER_SOURCE.SIGNUP
|
||||
);
|
||||
await this.usersService.attachUserGroup(['all_users'], signingUpOrganization.id, user.id, manager);
|
||||
|
||||
this.emailService
|
||||
|
|
@ -561,7 +628,13 @@ export class AuthService {
|
|||
|
||||
async addUserToTheWorkspace(existingUser: User, signingUpOrganization: Organization, manager: EntityManager) {
|
||||
await this.usersService.attachUserGroup(['all_users'], signingUpOrganization.id, existingUser.id, manager);
|
||||
return this.organizationUsersService.create(existingUser, signingUpOrganization, true, manager);
|
||||
return this.organizationUsersService.create(
|
||||
existingUser,
|
||||
signingUpOrganization,
|
||||
true,
|
||||
manager,
|
||||
WORKSPACE_USER_SOURCE.SIGNUP
|
||||
);
|
||||
}
|
||||
|
||||
async activateAccountWithToken(activateAccountWithToken: ActivateAccountWithTokenDto, response: any) {
|
||||
|
|
@ -846,10 +919,28 @@ export class AuthService {
|
|||
});
|
||||
await this.organizationUsersService.activateOrganization(organizationUser, manager);
|
||||
}
|
||||
return this.generateLoginResultPayload(response, user, organization, null, null, loggedInUser, manager);
|
||||
const isWorkspaceSignup = organizationUser.source === WORKSPACE_USER_SOURCE.SIGNUP;
|
||||
return this.generateLoginResultPayload(
|
||||
response,
|
||||
user,
|
||||
organization,
|
||||
null,
|
||||
isWorkspaceSignup,
|
||||
loggedInUser,
|
||||
manager
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async getInviteeDetails(token: string) {
|
||||
const organizationUser: OrganizationUser = await this.organizationUsersRepository.findOneOrFail({
|
||||
where: { invitationToken: token },
|
||||
select: ['id', 'user'],
|
||||
relations: ['user'],
|
||||
});
|
||||
return { email: organizationUser.user.email };
|
||||
}
|
||||
|
||||
async verifyInviteToken(token: string, organizationToken?: string) {
|
||||
const user: User = await this.usersRepository.findOne({ where: { invitationToken: token } });
|
||||
let organizationUser: OrganizationUser;
|
||||
|
|
@ -924,20 +1015,22 @@ export class AuthService {
|
|||
: user?.organizationIds?.includes(user?.defaultOrganizationId)
|
||||
? user.defaultOrganizationId
|
||||
: user?.organizationIds?.[0];
|
||||
const organizationDetails = currentOrganization
|
||||
const organizationDetails = currentOrganizationId
|
||||
? currentOrganization
|
||||
: await manager.findOneOrFail(Organization, {
|
||||
where: { id: currentOrganizationId },
|
||||
select: ['slug', 'name', 'id'],
|
||||
});
|
||||
? currentOrganization
|
||||
: await manager.findOneOrFail(Organization, {
|
||||
where: { id: currentOrganizationId },
|
||||
select: ['slug', 'name', 'id'],
|
||||
})
|
||||
: null;
|
||||
|
||||
return decamelizeKeys({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
currentOrganizationSlug: organizationDetails.slug,
|
||||
currentOrganizationName: organizationDetails.name,
|
||||
currentOrganizationSlug: organizationDetails?.slug,
|
||||
currentOrganizationName: organizationDetails?.name,
|
||||
currentOrganizationId,
|
||||
});
|
||||
});
|
||||
|
|
@ -1019,6 +1112,7 @@ export class AuthService {
|
|||
lastName,
|
||||
status: invitedUserStatus,
|
||||
organizationStatus,
|
||||
organizationUserSource,
|
||||
invitedOrganizationId,
|
||||
source,
|
||||
} = invitedUser;
|
||||
|
|
@ -1055,6 +1149,18 @@ export class AuthService {
|
|||
throw new NotAcceptableException(errorResponse);
|
||||
}
|
||||
|
||||
const isWorkspaceSignup =
|
||||
organizationStatus === WORKSPACE_USER_STATUS.INVITED &&
|
||||
!!organizationToken &&
|
||||
invitedUserStatus === USER_STATUS.ACTIVE &&
|
||||
organizationUserSource === WORKSPACE_USER_SOURCE.SIGNUP;
|
||||
if (isWorkspaceSignup) {
|
||||
/* Active user & Organization invite */
|
||||
const responseObj = {
|
||||
organizationUserSource,
|
||||
};
|
||||
return decamelizeKeys(responseObj);
|
||||
}
|
||||
/* Send back the organization invite url if the user has old workspace + account invitation URL */
|
||||
const doesUserHaveWorkspaceAndAccountInvite =
|
||||
organizationAndAccountInvite &&
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue