ToolJet/frontend/src/Editor/Components/PasswordInput.jsx
Johnson Cherian 3169d38d63
Release: Appbuilder S1 (#10081)
* fix : color for all new columns

* revert

* Fix: selection of copy of selected component for ease (#9818)

* fix: selection of copy of selected component for ease

* add pre selection for clonig as well

* add clone check

* fixes selection of components on empty canvas

* Fix: sizing issues in horizontal divider (#9942)

* fix horizontal divider sizing issues

* fix dark mode color in horizontal divider and remove unused class

* add custom fallback for images when not found (#9943)

* cherry pick error message log changes and fix tjdb error logs in debugger (#9951)

* Fix: mouse release on canvas when properties/styles values selected (#9732)

* fix: mouse release on canvas when properties/styles values selected

* fix: event name

* fix: rest api headers empty state while creating new query (#9729)

* fix: selection issue in table row while editing  (#9944)

* allow selection in table cell

* update classname for selection

* display date picker date as text instead of input in read only mode

* Add new revamped multiselect widget (#9837)

* init textinput revamp

* updated styles panel

* bugfix

* updates

* fix :: accordion

* fix :: styling

* add box shadow , additional property,tooltip

* fix conditional render for styles

* feat :: fixed order of each property and styles

* feat :: styling input

* bugfix

* feat :: add option to add icon

* add option to add icon

* adding option to toggle visibility

* updated password input with new design

* chnaging component location

* bugfix

* style fixes

* fix :: added loader

* updated :: few detailing

* few bugfixes

* fix :: for form widget label

* fixes

* added option to add icon color

* including label field for password input

* fix for label

* fix

* test fix backward compatibility for height

* updates

* revert

* adding key for distinguishing older and newer widgets

* testing

* test

* test

* update

* update

* migration testing

* limit vertical resizing in textinput

* testing

* throw test

* test

* adding check for label length

* fixing edge cases

* removing resize

* backward compatibility height

* backward compatibility

* number input review fixes

* added exposed items

* fixing csa

* ui fixes

* fix height compatibility

* feat :: csa for all inputs and exposed variables

* backward compatibility fixes and validation fixes

* fixes :: textinput positioning of loader and icon

* fix :: password input

* cleanup and fixes

* fixes

* cleanup

* fixes

* review fixes

* review fixes

* typo fix

* fix padding

* review fixes styles component panel

* fix naming

* fix padding

* feat :: toggle switch revamp

* init checkbox

* fixes

* fixes

* switch fixes

* validation fix

* fixes

* cleanup

* height fix

* fix height toggle

* updates

* fix :: icons position

* updates

* cleanup

* updates events , csa

* cleanup

* backward compatibility

* clean

* backward compatibility fix

* label fixed to one line

* feat :: change validation from properties

* ui fixes

* icon name

* removed 'px' text from tooltip

* added onchange event for checkbox

* fixes placeholder

* few updates :: removing label in form

* ui in form

* fire onchange

* update :: number input validation behaviour

* testing fixes

* added side handlers

* removing unwanted fx

* disabling fx for padding field

* ordering change

* fix

* label issue + restricted side handler

* fix :: box shadow bug

* fix

* on change event doesnt propagate exposed vars correctly

* adding debounce for slider value change

* fix :: for modal ooen bug during onfocus event

* test slider

* fix :: bugs regarding state update in checbox , slider , slider bug

* update slider with radix slider

* bugfix

* update tooltip

* fix toggle switch

* fixes : inspector

* fix : checkbox label

* Remove date-fns depedency from table datepicker

* Revert Remove date-fns depedency from table datepicker

* feat : checkbox completed

* update checkbox review changes

* feat : toggle component

* feat : added new toggle component

* fix : colors

* updated review changes

* update name for old and new version

* update

* case change

* update

* update icon

* removed padding from checkbox and toggle

* fix naming

* product review and bugfixes : changes

* fix : checkbox setvalue action

* Update setvalue action in toggle

* fixed: section for legacy and new components

* add new version of dropdown

* Add same styles as other input components

* fix height issues

* Add new revamped multiselect widget

* Fix design review

* fix design issues

* Fix

* Fix merge issues

* Add menu portal target

* resolve code comments

* widget config changes

* add hover for clear icon

* fix

* Fix review comments

* Multiselect changes after dropdown merge

* exposed variables

* Delete unused components

* Multiselect fixes

* Dropdown CSS fixes and multiselect fixes

* Fix merge issues

* fix

* Add highlight text

* Change multiselect UI

* fix error message

* fix multiselect opening closing

* placeholder fix

* fix highlighting in multiselect

* fix : testing bugs

* fix : default value

* Fix merge issues

* Fix Qa bugs

* Fix QA bugs

* Fix codehinter default values

* fix fireEvent on focus

* Fixes

* Provide minwidth to dropdown and multiselect

* Fix search input value not getting updated

---------

Co-authored-by: stepinfwd <stepinfwd@gmail.com>
Co-authored-by: Johnson Cherian <johnsonc.dev@gmail.com>

* Fix: remove mandatory key from password input (#9786)

* Remove date-fns depedency from table datepicker

* Revert Remove date-fns depedency from table datepicker

* remove mandatory key from password input

---------

Co-authored-by: Nakul Nagargade <nakul@tooljet.com>
Co-authored-by: Johnson Cherian <johnsonc.dev@gmail.com>

* feat : Query manager separated to different tabs (#9823)

* change toggle for query manager and revamp preview

* feat : query manger body revamp

* updates

* fix : tranformation switch

* preview integration

* loader safari changes and overflow fix

* fix

* fix : settings tab QM

* revert few changes to fix datasources page

* revert header options change

* zindex fix for query-pane

* fix : events ui

* fix :events widget manager

* code optimised for this file

* QM header fixes

* dark mode changes

* fix : info icon

* open preview drawer on run query

* fix : query manager query section icons update

* update color

* design feedbacks and make preview panel resizable

* update toggle for preview result & increate draggable area

* fix :review changes

* merge fixes

* Merge branch 'appbuilder-1.8' into feature/query-manager-body

* fix : codehinter in disabled state

* ui fix

* code refactor

* cleanup

* fix fontsize in datasource selector popup

* fix border issue in preview container and increase draggable area

* fix : review fixes

* fix: fixed text css formatting for safari support

* Revert "code refactor"

This reverts commit 4763dd11a3.

* typo

* fix : not able to select text in preview

* fix : not able to view add params

* fix selection issue in preview

* fix : add scroll in query  manager when preview is blocking view

---------

Co-authored-by: Kartik Gupta <gupta.kartik18kg@gmail.com>

* Fixes: select all click behaviour on label (#10108)

* fixes: select all click behaviour on label

* fix: legacy component names

* fix: selecto issue (#10107)

* Fix : Prevent component autofill (#10040)

* fix : prevent other component from autofilling data when password is filled from browser suggestions

* optimise

* feat: skip onDragStop execution if drag event is empty (#10118)

* feat: skip onDragStop execution if drag event is empty

* fix: added additonal logs for  error

* display query preview data in preview panel and display transformation failure stacktrace data in previewpanel as well (#10129)

* Fix while adding new rows in table components when ever entered the text and pressed enter it doubles the text (#10112)

* Integration bugfixes appbuilder 1.8 (#10109)

* fix : query maanager duplicate and preview issue

* fix : multiselect breaking on making dynamic options null

* fix : preview and query panel integration bugs

* fix : placeholder

* fix : doc links

* fix : scroll in TJDB filter section

* fix : portal for multiselect

* fixes

* fix : image column table alignment

* fix : doc link for multiselect

* fix : codehinter state being persited across components

* fix :export app qery manager items not coming in correct order

* fix: search icon position

* code refactor

---------

Co-authored-by: Johnson Cherian <johnsonc.dev@gmail.com>

* add z-index to app name info header container (#10116)

* Fix dropdown and multiselect crash on integer labels (#10128)

* cast integer labels to string

* add null check for label and provide default value for empty labels

* empty and null handle for schemas and other values

* revert changes

* Fix: dark mode on preview names (#10136)

* fix: dark mode of preview names

* fix background color of preview

* fix tjdb query import (#10134)

* fix :revert radio button name change (#10153)

* Fix: select issue on multiselect (#10137)

* remove portal from multiselect

* fix: dynamic values for options in dropdown/multiselect

* remove fx from default option

* Fix: delete on options delete in dropdown (#10192)

* fix: delete on options delete

* fix: overlapping of multiselect on parent container

* fix: outside click of multiselect

* hotfix : Table breaking on importing older apps with null value in column (#10185)

* fix : table breaking on importing older apps with null value in column

* fix : table crash , codehinter not saving values upon page change

* remove low priority wrapper from autosave

* remove logs

* added delay to autosave as callback

* fix: dropdown crash on invalid data (#10202)

* revert to previous transformation code , fix darkmode color (#10216)

* fix : doclink for dropdown (#10217)

* fix : Transformations value getting cleared / not getting saved (#10218)

* fix : transformation value not getting saved

* remove dependency

* chore: version update for release

---------

Co-authored-by: stepinfwd <stepinfwd@gmail.com>
Co-authored-by: vjaris42 <vjy239@gmail.com>
Co-authored-by: Kartik Gupta <gupta.kartik18kg@gmail.com>
Co-authored-by: Nakul Nagargade <133095394+nakulnagargade@users.noreply.github.com>
Co-authored-by: Nakul Nagargade <nakul@tooljet.com>
Co-authored-by: Akshay <akshaysasidharan93@gmail.com>
2024-07-01 08:46:22 +05:30

407 lines
13 KiB
JavaScript

import React, { useEffect, useRef, useState } from 'react';
import { resolveWidgetFieldValue } from '@/_helpers/utils';
import * as Icons from '@tabler/icons-react';
import Loader from '@/ToolJetUI/Loader/Loader';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import Label from '@/_ui/Label';
import { useEditorStore } from '@/_stores/editorStore';
export const PasswordInput = function PasswordInput({
height,
validate,
properties,
styles,
setExposedVariable,
fireEvent,
component,
darkMode,
dataCy,
isResizing,
id,
}) {
const textInputRef = useRef();
const labelRef = useRef();
const { loadingState, disabledState, label, placeholder } = properties;
const {
padding,
borderRadius,
borderColor,
backgroundColor,
textColor,
boxShadow,
width,
alignment,
direction,
color,
auto,
errTextColor,
iconColor,
accentColor,
} = styles;
const [disable, setDisable] = useState(disabledState || loadingState);
const [passwordValue, setPasswordValue] = useState(properties.value);
const [visibility, setVisibility] = useState(properties.visibility);
const { isValid, validationError } = validate(passwordValue);
const [showValidationError, setShowValidationError] = useState(false);
const isMandatory = resolveWidgetFieldValue(component?.definition?.validation?.mandatory?.value);
const [labelWidth, setLabelWidth] = useState(0);
const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side';
const [iconVisibility, setIconVisibility] = useState(false);
const [loading, setLoading] = useState(loadingState);
const [isFocused, setIsFocused] = useState(false);
const tinycolor = require('tinycolor2');
const _width = (width / 100) * 70; // Max width which label can go is 70% for better UX calculate width based on this value
const computedStyles = {
height: height == 36 ? (padding == 'default' ? '36px' : '40px') : padding == 'default' ? height : height + 4,
borderRadius: `${borderRadius}px`,
backgroundColor: !['#ffffff', '#fff'].includes(backgroundColor)
? backgroundColor
: disable || loading
? darkMode
? 'var(--surfaces-app-bg-default)'
: 'var(--surfaces-surface-03)'
: 'var(--surfaces-surface-01)',
boxShadow: boxShadow,
padding: styles.iconVisibility ? '8px 10px 8px 29px' : '8px 10px 8px 10px',
overflow: 'hidden',
textOverflow: 'ellipsis',
color: textColor !== '#1B1F24' ? textColor : disable || loading ? 'var(--text-disabled)' : 'var(--text-primary)',
borderColor: isFocused
? accentColor != '4368E3'
? accentColor
: 'var(--primary-accent-strong)'
: borderColor != '#CCD1D5'
? borderColor
: disable || loading
? '1px solid var(--borders-disabled-on-white)'
: 'var(--borders-default)',
'--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(),
};
const loaderStyle = {
right:
direction === 'right' &&
defaultAlignment === 'side' &&
((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0))
? `${labelWidth + 11}px`
: '11px',
top: `${
defaultAlignment === 'top'
? ((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)) &&
'calc(50% + 10px)'
: ''
}`,
transform:
defaultAlignment === 'top' &&
((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)) &&
' translateY(-50%)',
zIndex: 3,
};
useEffect(() => {
setExposedVariable('label', label);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [label]);
useEffect(() => {
if (labelRef?.current) {
const absolutewidth = labelRef?.current?.getBoundingClientRect()?.width;
setLabelWidth(absolutewidth);
} else setLabelWidth(0);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
isResizing,
width,
auto,
defaultAlignment,
component?.definition?.styles?.iconVisibility?.value,
label?.length,
isMandatory,
padding,
direction,
alignment,
isMandatory,
labelRef?.current?.getBoundingClientRect()?.width,
]);
useEffect(() => {
disable !== disabledState && setDisable(disabledState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disabledState]);
useEffect(() => {
visibility !== properties.visibility && setVisibility(properties.visibility);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.visibility]);
useEffect(() => {
loading !== loadingState && setLoading(loadingState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loadingState]);
useEffect(() => {
setExposedVariable('isValid', isValid);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isValid]);
useEffect(() => {
setPasswordValue(properties.value);
setExposedVariable('value', properties?.value ?? '');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.value]);
useEffect(() => {
setExposedVariable('setFocus', async function () {
textInputRef.current.focus();
});
setExposedVariable('setBlur', async function () {
textInputRef.current.blur();
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
setExposedVariable('setText', async function (text) {
setPasswordValue(text);
setExposedVariable('value', text);
fireEvent('onChange');
});
setExposedVariable('clear', async function () {
setPasswordValue('');
setExposedVariable('value', '');
fireEvent('onChange');
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setPasswordValue]);
const iconName = styles.icon; // Replace with the name of the icon you want
// eslint-disable-next-line import/namespace
const IconElement = Icons[iconName] == undefined ? Icons['IconHome2'] : Icons[iconName];
// eslint-disable-next-line import/namespace
useEffect(() => {
setExposedVariable('isMandatory', isMandatory);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMandatory]);
useEffect(() => {
setExposedVariable('isLoading', loading);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loading]);
useEffect(() => {
setExposedVariable('setLoading', async function (loading) {
setLoading(loading);
setExposedVariable('isLoading', loading);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.loadingState]);
useEffect(() => {
setExposedVariable('isVisible', visibility);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [visibility]);
useEffect(() => {
setExposedVariable('setVisibility', async function (state) {
setVisibility(state);
setExposedVariable('isVisible', state);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.visibility]);
useEffect(() => {
setExposedVariable('setDisable', async function (disable) {
setDisable(disable);
setExposedVariable('isDisabled', disable);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disabledState]);
useEffect(() => {
setExposedVariable('isDisabled', disable);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disable]);
const currentPageId = useEditorStore.getState().currentPageId;
const components = useEditorStore.getState().appDefinition?.pages?.[currentPageId]?.components || {};
const isChildOfForm = Object.keys(components).some((key) => {
if (key == id) {
const { parent } = components[key].component;
if (parent) {
const parentComponentTypes = {};
Object.keys(components).forEach((key) => {
const { component } = components[key];
parentComponentTypes[key] = component.component;
});
if (parentComponentTypes[parent] == 'Form') return true;
}
}
return false;
});
const renderInput = () => (
<>
<div
data-cy={`label-${String(component.name).toLowerCase()}`}
className={`text-input d-flex ${
defaultAlignment === 'top' &&
((width != 0 && label && label?.length != 0) || (auto && width == 0 && label && label?.length != 0))
? 'flex-column'
: 'align-items-center '
} ${direction === 'right' && defaultAlignment === 'side' ? 'flex-row-reverse' : ''}
${direction === 'right' && defaultAlignment === 'top' ? 'text-right' : ''}
${visibility || 'invisible'}`}
style={{
position: 'relative',
whiteSpace: 'nowrap',
width: '100%',
}}
>
<Label
label={label}
width={width}
labelRef={labelRef}
darkMode={darkMode}
color={color}
defaultAlignment={defaultAlignment}
direction={direction}
auto={auto}
isMandatory={isMandatory}
_width={_width}
labelWidth={labelWidth}
/>
{component?.definition?.styles?.iconVisibility?.value && !isResizing && (
<IconElement
data-cy={'text-input-icon'}
style={{
width: '16px',
height: '16px',
left:
direction === 'right'
? '11px'
: defaultAlignment === 'top'
? '11px'
: (label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)
? `${labelWidth + 11}px`
: '11px', //11 :: is 10 px inside the input + 1 px border + 12px margin right
position: 'absolute',
top: `${
defaultAlignment === 'side'
? '50%'
: (label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)
? 'calc(50% + 10px)'
: '50%'
}`,
transform: ' translateY(-50%)',
color: iconColor !== '#CFD3D859' ? iconColor : 'var(--icons-weak-disabled)',
zIndex: 3,
}}
stroke={1.5}
/>
)}
{!loading && !isResizing && (
<div
onClick={() => {
setIconVisibility(!iconVisibility);
}}
style={{
width: '16px',
height: '16px',
position: 'absolute',
right:
direction === 'right' &&
defaultAlignment === 'side' &&
((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0))
? `${labelWidth + 11}px`
: '11px',
top: `${
defaultAlignment === 'top'
? ((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)) &&
'calc(50% + 10px)'
: ''
}`,
transform:
defaultAlignment === 'top' &&
((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)) &&
' translateY(-50%)',
display: 'flex',
zIndex: 3,
}}
stroke={1.5}
>
<SolidIcon
width={16}
fill={'var(--icons-weak-disabled)'}
className="password-component-eye"
name={!iconVisibility ? 'eye1' : 'eyedisable'}
/>
</div>
)}
<input
data-cy={dataCy}
className={`tj-text-input-widget ${
!isValid && showValidationError ? 'is-invalid' : ''
} validation-without-icon `}
ref={textInputRef}
onKeyUp={(e) => {
if (e.key === 'Enter') {
setPasswordValue(e.target.value);
setExposedVariable('value', e.target.value);
fireEvent('onEnterPressed');
}
}}
onChange={(e) => {
setPasswordValue(e.target.value);
setExposedVariable('value', e.target.value);
fireEvent('onChange');
}}
onBlur={(e) => {
setIsFocused(false);
setShowValidationError(true);
e.stopPropagation();
fireEvent('onBlur');
}}
onFocus={(e) => {
setIsFocused(true);
e.stopPropagation();
setTimeout(() => {
fireEvent('onFocus');
}, 0);
}}
type={!iconVisibility ? 'password' : 'text'}
placeholder={placeholder}
style={computedStyles}
value={passwordValue}
disabled={disable || loading}
/>
{loading && <Loader style={{ ...loaderStyle }} width="16" />}
</div>
{showValidationError && visibility && (
<div
data-cy={`${String(component.name).toLowerCase()}-invalid-feedback`}
style={{
color: errTextColor !== '#D72D39' ? errTextColor : 'var(--status-error-strong)',
textAlign: direction == 'left' && 'end',
fontSize: '11px',
fontWeight: '400',
lineHeight: '16px',
}}
>
{showValidationError && validationError}
</div>
)}
</>
);
const renderContainer = (children) => {
return !isChildOfForm ? <form autoComplete="off">{children}</form> : <div>{children}</div>;
};
return renderContainer(renderInput());
};