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;
|
type: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
default?: string | number | boolean;
|
default?: string | number | boolean;
|
||||||
|
obscure?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppEnv {
|
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 { 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>
|
||||||
|
|||||||
Reference in New Issue
Block a user