fix: wire error prop to Input/Textarea components and eliminate duplicate loadTemplates on mount

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-13 02:47:23 -04:00
parent f988d70bca
commit 64b02159e0
2 changed files with 40 additions and 20 deletions

View File

@@ -15,9 +15,15 @@ export function ScriptFilterBar({ inputValue, setInputValue }: Props) {
const setCategory = useScriptGeneratorStore(s => s.setCategory)
const setSearch = useScriptGeneratorStore(s => s.setSearch)
// Debounce: 300ms after the input value settles, push to store
// Debounce: 300ms after the input value settles, push to store.
// Skip on initial mount (store.searchQuery is already '' and page already called loadTemplates).
const isFirstRender = useRef(true)
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false
return
}
if (debounceRef.current) clearTimeout(debounceRef.current)
debounceRef.current = setTimeout(() => {
setSearch(inputValue)

View File

@@ -28,7 +28,12 @@ export function ScriptParameterField({ param, value, error, disabled }: Props) {
let input: React.ReactNode
// Track whether the shared Input/Textarea component renders the error internally
// (so we skip the manual <p> at the bottom for these types)
let errorRenderedByComponent = false
if (param.type === 'text' || param.type === 'multi_text' || param.type === 'number') {
errorRenderedByComponent = true
input = (
<Input
id={id}
@@ -41,31 +46,37 @@ export function ScriptParameterField({ param, value, error, disabled }: Props) {
: (param.placeholder ?? undefined)
}
disabled={disabled}
error={error}
/>
)
} else if (param.type === 'password') {
errorRenderedByComponent = true
input = (
<div className="relative">
<Input
id={id}
type={showPassword ? 'text' : 'password'}
value={value}
onChange={handleChange}
placeholder={param.placeholder ?? undefined}
disabled={disabled}
/>
<button
type="button"
onClick={() => setShowPassword(v => !v)}
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
tabIndex={-1}
aria-label={showPassword ? 'Hide password' : 'Show password'}
>
{showPassword ? <EyeOff size={14} /> : <Eye size={14} />}
</button>
<div className="flex flex-col gap-1">
<div className="relative">
<Input
id={id}
type={showPassword ? 'text' : 'password'}
value={value}
onChange={handleChange}
placeholder={param.placeholder ?? undefined}
disabled={disabled}
error={error}
/>
<button
type="button"
onClick={() => setShowPassword(v => !v)}
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
tabIndex={-1}
aria-label={showPassword ? 'Hide password' : 'Show password'}
>
{showPassword ? <EyeOff size={14} /> : <Eye size={14} />}
</button>
</div>
</div>
)
} else if (param.type === 'textarea') {
errorRenderedByComponent = true
input = (
<Textarea
id={id}
@@ -74,6 +85,7 @@ export function ScriptParameterField({ param, value, error, disabled }: Props) {
placeholder={param.placeholder ?? undefined}
disabled={disabled}
rows={4}
error={error}
/>
)
} else if (param.type === 'select') {
@@ -111,12 +123,14 @@ export function ScriptParameterField({ param, value, error, disabled }: Props) {
)
} else {
// Fallback for unknown types
errorRenderedByComponent = true
input = (
<Input
id={id}
value={value}
onChange={handleChange}
disabled={disabled}
error={error}
/>
)
}
@@ -136,7 +150,7 @@ export function ScriptParameterField({ param, value, error, disabled }: Props) {
{param.help_text && (
<p className="text-xs text-muted-foreground mt-1">{param.help_text}</p>
)}
{error && (
{!errorRenderedByComponent && error && (
<p className="mt-1.5 text-xs text-red-400">{error}</p>
)}
</div>