support App envs obscured input text (#516)

* support App envs obscured input text

* wip
This commit is contained in:
Hoan Luu Huu
2025-05-10 20:13:23 +07:00
committed by GitHub
parent 46727f621b
commit 020b11e8ef
4 changed files with 147 additions and 31 deletions

View File

@@ -789,6 +789,7 @@ export interface AppEnvProperty {
type: string;
required?: boolean;
default?: string | number | boolean;
obscure?: boolean;
}
export interface AppEnv {

View 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;

View 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;
}
}
}

View File

@@ -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>