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; type: string;
required?: boolean; required?: boolean;
default?: string | number | boolean; default?: string | number | boolean;
obscure?: boolean;
} }
export interface AppEnv { 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 { hasLength, isUserAccountScope, useRedirect } from "src/utils";
import { setAccountFilter, setLocation } from "src/store/localStore"; import { setAccountFilter, setLocation } from "src/store/localStore";
import SpeechProviderSelection from "./speech-selection"; import SpeechProviderSelection from "./speech-selection";
import ObscureInput from "src/components/obscure-input";
type ApplicationFormProps = { type ApplicationFormProps = {
application?: UseApiDataMap<Application>; application?: UseApiDataMap<Application>;
@@ -811,39 +812,74 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
)} )}
</Tooltip> </Tooltip>
</label> </label>
<input {webhook.webhookEnv![key].obscure ? (
id={`env_${key}`} <ObscureInput
type={isNumber ? "number" : "text"} name={`env_${key}`}
name={`env_${key}`} id={`env_${key}`}
placeholder={ placeholder={
webhook.webhookEnv![key].description 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;
} }
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({ setEnvVars({
...(envVars || {}), ...(envVars || {}),
[key]: newValue, [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> </div>