mirror of
https://github.com/clucraft/PriceGhost.git
synced 2026-05-15 10:52:36 +02:00
Add password visibility toggle for sensitive fields in Settings
- Create reusable PasswordInput component with eye icon toggle - Apply to Telegram bot token, Discord webhook, Pushover keys - Apply to Anthropic and OpenAI API keys - Toggle switches between masked and visible text Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
082aae8789
commit
81bbd8538f
3 changed files with 85 additions and 13 deletions
77
frontend/src/components/PasswordInput.tsx
Normal file
77
frontend/src/components/PasswordInput.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { useState, InputHTMLAttributes } from 'react';
|
||||
|
||||
interface PasswordInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
|
||||
// All standard input props are inherited
|
||||
}
|
||||
|
||||
export default function PasswordInput({ style, ...props }: PasswordInputProps) {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative', display: 'flex' }}>
|
||||
<input
|
||||
{...props}
|
||||
type={visible ? 'text' : 'password'}
|
||||
style={{
|
||||
...style,
|
||||
width: '100%',
|
||||
paddingRight: '2.5rem',
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setVisible(!visible)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: '0.5rem',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '0.25rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'var(--text-muted)',
|
||||
}}
|
||||
title={visible ? 'Hide' : 'Show'}
|
||||
aria-label={visible ? 'Hide password' : 'Show password'}
|
||||
>
|
||||
{visible ? (
|
||||
// Eye-off icon (hidden)
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94" />
|
||||
<path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19" />
|
||||
<path d="M14.12 14.12a3 3 0 1 1-4.24-4.24" />
|
||||
<line x1="1" y1="1" x2="23" y2="23" />
|
||||
</svg>
|
||||
) : (
|
||||
// Eye icon (visible)
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Layout from '../components/Layout';
|
||||
import PasswordInput from '../components/PasswordInput';
|
||||
import {
|
||||
settingsApi,
|
||||
profileApi,
|
||||
|
|
@ -983,8 +984,7 @@ export default function Settings() {
|
|||
|
||||
<div className="settings-form-group">
|
||||
<label>Bot Token</label>
|
||||
<input
|
||||
type="password"
|
||||
<PasswordInput
|
||||
value={telegramBotToken}
|
||||
onChange={(e) => setTelegramBotToken(e.target.value)}
|
||||
placeholder={notificationSettings?.telegram_configured ? '••••••••••••••••' : 'Enter your bot token'}
|
||||
|
|
@ -1053,8 +1053,7 @@ export default function Settings() {
|
|||
|
||||
<div className="settings-form-group">
|
||||
<label>Webhook URL</label>
|
||||
<input
|
||||
type="password"
|
||||
<PasswordInput
|
||||
value={discordWebhookUrl}
|
||||
onChange={(e) => setDiscordWebhookUrl(e.target.value)}
|
||||
placeholder={notificationSettings?.discord_configured ? '••••••••••••••••' : 'https://discord.com/api/webhooks/...'}
|
||||
|
|
@ -1112,8 +1111,7 @@ export default function Settings() {
|
|||
|
||||
<div className="settings-form-group">
|
||||
<label>User Key</label>
|
||||
<input
|
||||
type="password"
|
||||
<PasswordInput
|
||||
value={pushoverUserKey}
|
||||
onChange={(e) => setPushoverUserKey(e.target.value)}
|
||||
placeholder={notificationSettings?.pushover_configured ? '••••••••••••••••' : 'Enter your user key'}
|
||||
|
|
@ -1123,8 +1121,7 @@ export default function Settings() {
|
|||
|
||||
<div className="settings-form-group">
|
||||
<label>Application API Token</label>
|
||||
<input
|
||||
type="password"
|
||||
<PasswordInput
|
||||
value={pushoverAppToken}
|
||||
onChange={(e) => setPushoverAppToken(e.target.value)}
|
||||
placeholder={notificationSettings?.pushover_configured ? '••••••••••••••••' : 'Enter your app token'}
|
||||
|
|
@ -1208,8 +1205,7 @@ export default function Settings() {
|
|||
{aiProvider === 'anthropic' && (
|
||||
<div className="settings-form-group">
|
||||
<label>Anthropic API Key</label>
|
||||
<input
|
||||
type="password"
|
||||
<PasswordInput
|
||||
value={anthropicApiKey}
|
||||
onChange={(e) => setAnthropicApiKey(e.target.value)}
|
||||
placeholder={aiSettings?.anthropic_configured ? '••••••••••••••••' : 'sk-ant-...'}
|
||||
|
|
@ -1227,8 +1223,7 @@ export default function Settings() {
|
|||
{aiProvider === 'openai' && (
|
||||
<div className="settings-form-group">
|
||||
<label>OpenAI API Key</label>
|
||||
<input
|
||||
type="password"
|
||||
<PasswordInput
|
||||
value={openaiApiKey}
|
||||
onChange={(e) => setOpenaiApiKey(e.target.value)}
|
||||
placeholder={aiSettings?.openai_configured ? '••••••••••••••••' : 'sk-...'}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue