mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2025-12-19 05:37:43 +00:00
support App envs obscured input text (#516)
* support App envs obscured input text * wip
This commit is contained in:
@@ -789,6 +789,7 @@ export interface AppEnvProperty {
|
||||
type: string;
|
||||
required?: boolean;
|
||||
default?: string | number | boolean;
|
||||
obscure?: boolean;
|
||||
}
|
||||
|
||||
export interface AppEnv {
|
||||
|
||||
40
src/components/obscure-input/index.tsx
Normal file
40
src/components/obscure-input/index.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React, { useState } from "react";
|
||||
import { Icons } from "src/components/icons";
|
||||
import "./styles.scss";
|
||||
|
||||
interface ObscureInputProps
|
||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type"> {
|
||||
value: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
export const ObscureInput = ({
|
||||
value,
|
||||
onChange,
|
||||
className = "",
|
||||
...props
|
||||
}: ObscureInputProps) => {
|
||||
const [revealed, setRevealed] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="passwd">
|
||||
<input
|
||||
type={revealed ? "text" : "password"}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className={className}
|
||||
{...props}
|
||||
/>
|
||||
<button
|
||||
className="btnty"
|
||||
type="button"
|
||||
onClick={() => setRevealed(!revealed)}
|
||||
aria-label={revealed ? "Hide text" : "Show text"}
|
||||
>
|
||||
{revealed ? <Icons.EyeOff /> : <Icons.Eye />}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ObscureInput;
|
||||
39
src/components/obscure-input/styles.scss
Normal file
39
src/components/obscure-input/styles.scss
Normal file
@@ -0,0 +1,39 @@
|
||||
@use "src/styles/vars";
|
||||
@use "@jambonz/ui-kit/src/styles/vars" as ui-vars;
|
||||
@use "@jambonz/ui-kit/src/styles/mixins" as ui-mixins;
|
||||
|
||||
.obscure-input {
|
||||
position: relative; // This is correct
|
||||
width: 100%;
|
||||
display: block; // Add this to ensure proper containing block
|
||||
|
||||
&__field {
|
||||
width: 100%;
|
||||
padding-right: 40px;
|
||||
font-family: ui-vars.$font-mono;
|
||||
box-sizing: border-box; // Add this to ensure padding doesn't expand width
|
||||
}
|
||||
|
||||
&__toggle {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
height: 100%; // Make the button take full height of input
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2; // Ensure button is above input
|
||||
|
||||
svg {
|
||||
stroke: ui-vars.$jambonz;
|
||||
pointer-events: none;
|
||||
width: 18px; // Control icon size
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@ import { MSG_REQUIRED_FIELDS, MSG_WEBHOOK_FIELDS } from "src/constants";
|
||||
import { hasLength, isUserAccountScope, useRedirect } from "src/utils";
|
||||
import { setAccountFilter, setLocation } from "src/store/localStore";
|
||||
import SpeechProviderSelection from "./speech-selection";
|
||||
import ObscureInput from "src/components/obscure-input";
|
||||
|
||||
type ApplicationFormProps = {
|
||||
application?: UseApiDataMap<Application>;
|
||||
@@ -811,39 +812,74 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
)}
|
||||
</Tooltip>
|
||||
</label>
|
||||
<input
|
||||
id={`env_${key}`}
|
||||
type={isNumber ? "number" : "text"}
|
||||
name={`env_${key}`}
|
||||
placeholder={
|
||||
webhook.webhookEnv![key].description
|
||||
}
|
||||
required={webhook.webhookEnv![key].required}
|
||||
value={
|
||||
envVars && envVars[key] !== undefined
|
||||
? String(envVars[key])
|
||||
: defaultValue !== undefined
|
||||
? String(defaultValue)
|
||||
: ""
|
||||
}
|
||||
onChange={(e) => {
|
||||
// Convert to proper type based on schema
|
||||
let newValue;
|
||||
if (isNumber) {
|
||||
newValue =
|
||||
e.target.value === ""
|
||||
? ""
|
||||
: Number(e.target.value);
|
||||
} else {
|
||||
newValue = e.target.value;
|
||||
{webhook.webhookEnv![key].obscure ? (
|
||||
<ObscureInput
|
||||
name={`env_${key}`}
|
||||
id={`env_${key}`}
|
||||
placeholder={
|
||||
webhook.webhookEnv![key].description
|
||||
}
|
||||
required={webhook.webhookEnv![key].required}
|
||||
value={
|
||||
envVars && envVars[key] !== undefined
|
||||
? String(envVars[key])
|
||||
: defaultValue !== undefined
|
||||
? String(defaultValue)
|
||||
: ""
|
||||
}
|
||||
onChange={(e) => {
|
||||
// Convert to proper type based on schema
|
||||
let newValue;
|
||||
if (isNumber) {
|
||||
newValue =
|
||||
e.target.value === ""
|
||||
? ""
|
||||
: Number(e.target.value);
|
||||
} else {
|
||||
newValue = e.target.value;
|
||||
}
|
||||
|
||||
setEnvVars({
|
||||
...(envVars || {}),
|
||||
[key]: newValue,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
setEnvVars({
|
||||
...(envVars || {}),
|
||||
[key]: newValue,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
id={`env_${key}`}
|
||||
type={isNumber ? "number" : "text"}
|
||||
name={`env_${key}`}
|
||||
placeholder={
|
||||
webhook.webhookEnv![key].description
|
||||
}
|
||||
required={webhook.webhookEnv![key].required}
|
||||
value={
|
||||
envVars && envVars[key] !== undefined
|
||||
? String(envVars[key])
|
||||
: defaultValue !== undefined
|
||||
? String(defaultValue)
|
||||
: ""
|
||||
}
|
||||
onChange={(e) => {
|
||||
// Convert to proper type based on schema
|
||||
let newValue;
|
||||
if (isNumber) {
|
||||
newValue =
|
||||
e.target.value === ""
|
||||
? ""
|
||||
: Number(e.target.value);
|
||||
} else {
|
||||
newValue = e.target.value;
|
||||
}
|
||||
|
||||
setEnvVars({
|
||||
...(envVars || {}),
|
||||
[key]: newValue,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user