passwort anzeigen

This commit is contained in:
2026-05-25 17:10:09 +02:00
parent fcd13e6a40
commit 4a2e94bc83
18 changed files with 359 additions and 46 deletions

View File

@@ -0,0 +1,85 @@
import { useState } from 'react'
import { FaEye, FaEyeSlash, FaCheck } from 'react-icons/fa6'
/**
* Anzeige mit Auge: Klick kopiert den Wert und schaltet Sichtbarkeit um.
*/
export default function CopyableCredential({ value }) {
const [visible, setVisible] = useState(false)
const [copied, setCopied] = useState(false)
const text = (value || '').trim()
if (!text) {
return <span style={{ color: '#9ca3af' }}></span>
}
const handleClick = async (e) => {
e.preventDefault()
e.stopPropagation()
setVisible((v) => !v)
try {
await navigator.clipboard.writeText(text)
} catch {
const ta = document.createElement('textarea')
ta.value = text
ta.setAttribute('readonly', '')
ta.style.position = 'fixed'
ta.style.left = '-9999px'
document.body.appendChild(ta)
ta.select()
document.execCommand('copy')
document.body.removeChild(ta)
}
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
const masked = '•'.repeat(Math.min(Math.max(text.length, 8), 16))
const display = visible ? text : masked
return (
<span
className="copyable-credential"
style={{ display: 'inline-flex', alignItems: 'center', gap: '6px', maxWidth: '100%' }}
>
<span
style={{
fontFamily: visible ? 'inherit' : 'monospace',
letterSpacing: visible ? 'normal' : '1px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
maxWidth: '180px',
}}
title={visible ? text : 'Klick auf das Auge zum Anzeigen und Kopieren'}
>
{display}
</span>
<button
type="button"
className="btn"
onClick={handleClick}
title={copied ? 'Kopiert!' : 'Anzeigen und in Zwischenablage kopieren'}
style={{
padding: '2px 6px',
minWidth: '28px',
lineHeight: 1,
flexShrink: 0,
}}
aria-label={copied ? 'Kopiert' : 'Anzeigen und kopieren'}
>
{copied ? (
<FaCheck style={{ color: '#059669' }} />
) : visible ? (
<FaEyeSlash />
) : (
<FaEye />
)}
</button>
</span>
)
}
export function getCustomerPortalPassword(customer) {
return customer?.portalPassword || customer?.password || ''
}

View File

@@ -54,8 +54,12 @@ export function useCustomers() {
const createCustomer = async (data) => {
if (DEMO_MODE) {
const { password: _pw, ...rest } = data
const newCustomer = { ...rest, $id: Date.now().toString() }
const { password, ...rest } = data
const newCustomer = {
...rest,
portalPassword: password || rest.portalPassword || '',
$id: Date.now().toString(),
}
setCustomers(prev => [...prev, newCustomer])
return { success: true, data: newCustomer }
}
@@ -63,7 +67,10 @@ export function useCustomers() {
try {
const { password, ...fields } = data
const result = await createCustomerWithPortalAccess({ ...fields, password })
const customer = result.customer
const customer = {
...result.customer,
portalPassword: result.customer?.portalPassword || password || '',
}
setCustomers(prev => [...prev, customer])
return { success: true, data: customer }
} catch (err) {
@@ -73,8 +80,15 @@ export function useCustomers() {
const updateCustomer = async (id, data) => {
if (DEMO_MODE) {
const { password: _pw, ...rest } = data
setCustomers(prev => prev.map(c => c.$id === id ? { ...c, ...rest } : c))
const { password, ...rest } = data
setCustomers(prev =>
prev.map(c => {
if (c.$id !== id) return c
const next = { ...c, ...rest }
if (password) next.portalPassword = password
return next
})
)
return { success: true }
}
@@ -84,8 +98,22 @@ export function useCustomers() {
if (password) payload.password = password
const result = await updateCustomerWithPortalAccess(id, payload)
const customer = result.customer
setCustomers(prev => prev.map(c => c.$id === id ? customer : c))
const customer = {
...result.customer,
portalPassword:
result.customer?.portalPassword ||
password ||
undefined,
}
setCustomers(prev =>
prev.map(c => {
if (c.$id !== id) return c
return {
...customer,
portalPassword: customer.portalPassword ?? c.portalPassword,
}
})
)
return { success: true, data: customer }
} catch (err) {
return { success: false, error: err.message }

View File

@@ -5,6 +5,7 @@ import { useCustomers } from '../hooks/useCustomers'
import { useEmployees } from '../hooks/useEmployees'
import { FaPlus, FaTrash, FaFloppyDisk, FaSpinner } from 'react-icons/fa6'
import { FaEdit } from 'react-icons/fa'
import CopyableCredential, { getCustomerPortalPassword } from '../components/CopyableCredential'
export default function AdminPage() {
const { user, isAdmin } = useAuth()
@@ -398,6 +399,7 @@ export default function AdminPage() {
<th>Name</th>
<th>Location</th>
<th>Email</th>
<th>Passwort</th>
<th>Phone</th>
<th>Portal</th>
<th>Aktionen</th>
@@ -499,7 +501,12 @@ export default function AdminPage() {
<td>{customer.code || '-'}</td>
<td>{customer.name || '-'}</td>
<td>{customer.location || '-'}</td>
<td>{customer.email || '-'}</td>
<td>
<CopyableCredential value={customer.email} />
</td>
<td>
<CopyableCredential value={getCustomerPortalPassword(customer)} />
</td>
<td>{customer.phone || '-'}</td>
<td>
{customer.portalAccessEnabled ? (