diff --git a/frontend/src/Components/Form/FormInputGroup.js b/frontend/src/Components/Form/FormInputGroup.js index a00f6f8de..a74d8e05a 100644 --- a/frontend/src/Components/Form/FormInputGroup.js +++ b/frontend/src/Components/Form/FormInputGroup.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React from 'react'; +import PrivacyTextInput from 'Components/Form/PrivacyTextInput'; import Link from 'Components/Link/Link'; import { inputTypes, kinds } from 'Helpers/Props'; import translate from 'Utilities/String/translate'; @@ -97,6 +98,9 @@ function getComponent(type) { case inputTypes.TAG_SELECT: return TagSelectInputConnector; + case inputTypes.PRIVACY_TEXT: + return PrivacyTextInput; + default: return TextInput; } diff --git a/frontend/src/Components/Form/PrivacyTextInput.css b/frontend/src/Components/Form/PrivacyTextInput.css new file mode 100644 index 000000000..ef6a0932e --- /dev/null +++ b/frontend/src/Components/Form/PrivacyTextInput.css @@ -0,0 +1,17 @@ +.container { + position: relative; +} + +.input { + composes: input from '~Components/Form/TextInput.css'; + + padding: 6px 30px 6px 16px; +} + +.toggle { + position: absolute; + margin-left: -25px; + width: 20px; + text-align: center; + line-height: 35px; +} diff --git a/frontend/src/Components/Form/PrivacyTextInput.css.d.ts b/frontend/src/Components/Form/PrivacyTextInput.css.d.ts new file mode 100644 index 000000000..a7d07a37b --- /dev/null +++ b/frontend/src/Components/Form/PrivacyTextInput.css.d.ts @@ -0,0 +1,9 @@ +// This file is automatically generated. +// Please do not change this file! +interface CssExports { + 'container': string; + 'input': string; + 'toggle': string; +} +export const cssExports: CssExports; +export default cssExports; diff --git a/frontend/src/Components/Form/PrivacyTextInput.tsx b/frontend/src/Components/Form/PrivacyTextInput.tsx new file mode 100644 index 000000000..5407349cb --- /dev/null +++ b/frontend/src/Components/Form/PrivacyTextInput.tsx @@ -0,0 +1,46 @@ +import React, { SyntheticEvent, useCallback, useState } from 'react'; +import IconButton from 'Components/Link/IconButton'; +import { icons } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; +import TextInput from './TextInput'; +import styles from './PrivacyTextInput.css'; + +interface PrivacyTextInputProps { + className: string; +} + +function PrivacyTextInput(props: PrivacyTextInputProps) { + const { className = styles.input, ...otherProps } = props; + + const [isVisible, setIsVisible] = useState(false); + + const toggleVisibility = useCallback(() => { + setIsVisible(!isVisible); + }, [isVisible, setIsVisible]); + + // Prevent a user from copying (or cutting) the password from the input + const onCopy = useCallback((event: SyntheticEvent) => { + event.preventDefault(); + event.nativeEvent.stopImmediatePropagation(); + }, []); + + return ( +
+ + + +
+ ); +} + +export default PrivacyTextInput; diff --git a/frontend/src/Components/Form/ProviderFieldFormGroup.js b/frontend/src/Components/Form/ProviderFieldFormGroup.js index 878e3a7ce..908931e9d 100644 --- a/frontend/src/Components/Form/ProviderFieldFormGroup.js +++ b/frontend/src/Components/Form/ProviderFieldFormGroup.js @@ -6,7 +6,7 @@ import FormInputGroup from 'Components/Form/FormInputGroup'; import FormLabel from 'Components/Form/FormLabel'; import { inputTypes } from 'Helpers/Props'; -function getType({ type, selectOptionsProviderAction }) { +function getType({ type, selectOptionsProviderAction, privacy }) { switch (type) { case 'captcha': return inputTypes.CAPTCHA; @@ -34,6 +34,9 @@ function getType({ type, selectOptionsProviderAction }) { case 'tagSelect': return inputTypes.TAG_SELECT; case 'textbox': + if (privacy === 'userName' || privacy === 'apiKey') { + return inputTypes.PRIVACY_TEXT; + } return inputTypes.TEXT; case 'oAuth': return inputTypes.OAUTH; diff --git a/frontend/src/Helpers/Props/icons.js b/frontend/src/Helpers/Props/icons.js index 834452242..1bf0e69cd 100644 --- a/frontend/src/Helpers/Props/icons.js +++ b/frontend/src/Helpers/Props/icons.js @@ -56,6 +56,7 @@ import { faExclamationTriangle as fasExclamationTriangle, faExternalLinkAlt as fasExternalLinkAlt, faEye as fasEye, + faEyeSlash as fasEyeSlash, faFastBackward as fasFastBackward, faFastForward as fasFastForward, faFileExport as fasFileExport, @@ -245,3 +246,4 @@ export const VIEW = fasEye; export const WARNING = fasExclamationTriangle; export const WIKI = fasBookReader; export const BLOCKLIST = fasBan; +export const HIDE = fasEyeSlash; diff --git a/frontend/src/Helpers/Props/inputTypes.js b/frontend/src/Helpers/Props/inputTypes.js index 6c4564341..2b2e59b1a 100644 --- a/frontend/src/Helpers/Props/inputTypes.js +++ b/frontend/src/Helpers/Props/inputTypes.js @@ -20,6 +20,7 @@ export const SELECT = 'select'; export const DYNAMIC_SELECT = 'dynamicSelect'; export const TAG = 'tag'; export const TEXT = 'text'; +export const PRIVACY_TEXT = 'privacyText'; export const TEXT_AREA = 'textArea'; export const TEXT_TAG = 'textTag'; export const TAG_SELECT = 'tagSelect'; @@ -46,6 +47,7 @@ export const all = [ DYNAMIC_SELECT, TAG, TEXT, + PRIVACY_TEXT, TEXT_AREA, TEXT_TAG, TAG_SELECT diff --git a/src/Prowlarr.Http/ClientSchema/Field.cs b/src/Prowlarr.Http/ClientSchema/Field.cs index 7ceed1ae9..5053cacee 100644 --- a/src/Prowlarr.Http/ClientSchema/Field.cs +++ b/src/Prowlarr.Http/ClientSchema/Field.cs @@ -20,6 +20,7 @@ namespace Prowlarr.Http.ClientSchema public string SelectOptionsProviderAction { get; set; } public string Section { get; set; } public string Hidden { get; set; } + public PrivacyLevel Privacy { get; set; } public string Placeholder { get; set; } public bool IsFloat { get; set; } diff --git a/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs b/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs index 171c9db04..39e3e1ba3 100644 --- a/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs @@ -105,6 +105,7 @@ namespace Prowlarr.Http.ClientSchema Advanced = fieldAttribute.Advanced, Type = fieldAttribute.Type.ToString().FirstCharToLower(), Section = fieldAttribute.Section, + Privacy = fieldAttribute.Privacy, Placeholder = fieldAttribute.Placeholder };