Admin: Kunden mit Portal-Passwort für project.webklar.com anlegen.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Webklar Deploy
2026-05-25 06:47:01 +00:00
parent fda673702e
commit fcd13e6a40
3 changed files with 133 additions and 25 deletions

View File

@@ -1,5 +1,9 @@
import { useState, useEffect, useCallback } from 'react'
import { databases, DATABASE_ID, COLLECTIONS, ID, Query, isDemoMode } from '../lib/appwrite'
import {
createCustomerWithPortalAccess,
updateCustomerWithPortalAccess,
} from '../lib/customerAdminApi'
const DEMO_MODE = isDemoMode
@@ -50,20 +54,18 @@ export function useCustomers() {
const createCustomer = async (data) => {
if (DEMO_MODE) {
const newCustomer = { ...data, $id: Date.now().toString() }
const { password: _pw, ...rest } = data
const newCustomer = { ...rest, $id: Date.now().toString() }
setCustomers(prev => [...prev, newCustomer])
return { success: true, data: newCustomer }
}
try {
const response = await databases.createDocument(
DATABASE_ID,
COLLECTIONS.CUSTOMERS,
ID.unique(),
data
)
setCustomers(prev => [...prev, response])
return { success: true, data: response }
const { password, ...fields } = data
const result = await createCustomerWithPortalAccess({ ...fields, password })
const customer = result.customer
setCustomers(prev => [...prev, customer])
return { success: true, data: customer }
} catch (err) {
return { success: false, error: err.message }
}
@@ -71,19 +73,20 @@ export function useCustomers() {
const updateCustomer = async (id, data) => {
if (DEMO_MODE) {
setCustomers(prev => prev.map(c => c.$id === id ? { ...c, ...data } : c))
const { password: _pw, ...rest } = data
setCustomers(prev => prev.map(c => c.$id === id ? { ...c, ...rest } : c))
return { success: true }
}
try {
const response = await databases.updateDocument(
DATABASE_ID,
COLLECTIONS.CUSTOMERS,
id,
data
)
setCustomers(prev => prev.map(c => c.$id === id ? response : c))
return { success: true, data: response }
const { password, ...fields } = data
const payload = { ...fields }
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))
return { success: true, data: customer }
} catch (err) {
return { success: false, error: err.message }
}

View File

@@ -0,0 +1,32 @@
import { account } from './appwrite'
const PROJECT_ADMIN_URL =
import.meta.env.VITE_PROJECT_ADMIN_URL || 'https://project.webklar.com'
async function adminFetch(path, { method = 'POST', body } = {}) {
const jwt = await account.createJWT()
const response = await fetch(`${PROJECT_ADMIN_URL}${path}`, {
method,
headers: {
Authorization: `Bearer ${jwt.jwt}`,
'Content-Type': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
})
const data = await response.json().catch(() => ({}))
if (!response.ok) {
throw new Error(data.error || `API-Fehler ${response.status}`)
}
return data
}
export async function createCustomerWithPortalAccess(payload) {
return adminFetch('/api/admin/customers', { method: 'POST', body: payload })
}
export async function updateCustomerWithPortalAccess(customerId, payload) {
return adminFetch(`/api/admin/customers/${customerId}`, {
method: 'PATCH',
body: payload,
})
}

View File

@@ -27,7 +27,22 @@ export default function AdminPage() {
const [saving, setSaving] = useState(false)
const [saveMessage, setSaveMessage] = useState('')
const [editingCustomer, setEditingCustomer] = useState(null)
const [customerForm, setCustomerForm] = useState({ code: '', name: '', location: '', email: '', phone: '' })
const [customerForm, setCustomerForm] = useState({
code: '',
name: '',
location: '',
email: '',
phone: '',
password: '',
})
const emptyCustomerForm = {
code: '',
name: '',
location: '',
email: '',
phone: '',
password: '',
}
const [editingEmployee, setEditingEmployee] = useState(null)
const [employeeForm, setEmployeeForm] = useState({ userId: '', displayName: '', email: '', shortcode: '' })
const [adminSection, setAdminSection] = useState('config')
@@ -384,6 +399,7 @@ export default function AdminPage() {
<th>Location</th>
<th>Email</th>
<th>Phone</th>
<th>Portal</th>
<th>Aktionen</th>
</tr>
</thead>
@@ -433,14 +449,34 @@ export default function AdminPage() {
onChange={(e) => setCustomerForm(prev => ({ ...prev, phone: e.target.value }))}
/>
</td>
<td>
<input
type="password"
className="form-control"
value={customerForm.password}
onChange={(e) => setCustomerForm(prev => ({ ...prev, password: e.target.value }))}
placeholder="Neu (optional)"
autoComplete="new-password"
/>
</td>
<td>
<button
className="btn btn-green"
onClick={async () => {
if (!customerForm.email?.trim()) {
alert('E-Mail ist für den Portal-Login erforderlich.')
return
}
if (customerForm.password && customerForm.password.length < 8) {
alert('Passwort muss mindestens 8 Zeichen haben.')
return
}
const result = await updateCustomer(customer.$id, customerForm)
if (result.success) {
setEditingCustomer(null)
setCustomerForm({ code: '', name: '', location: '', email: '', phone: '' })
setCustomerForm(emptyCustomerForm)
} else {
alert('Fehler beim Speichern: ' + (result.error || 'Unbekannter Fehler'))
}
}}
>
@@ -450,7 +486,7 @@ export default function AdminPage() {
className="btn"
onClick={() => {
setEditingCustomer(null)
setCustomerForm({ code: '', name: '', location: '', email: '', phone: '' })
setCustomerForm(emptyCustomerForm)
}}
style={{ marginLeft: '4px' }}
>
@@ -465,6 +501,13 @@ export default function AdminPage() {
<td>{customer.location || '-'}</td>
<td>{customer.email || '-'}</td>
<td>{customer.phone || '-'}</td>
<td>
{customer.portalAccessEnabled ? (
<span style={{ color: '#059669', fontWeight: 600 }}>aktiv</span>
) : (
<span style={{ color: '#b45309' }}>inaktiv</span>
)}
</td>
<td>
<button
className="btn"
@@ -475,7 +518,8 @@ export default function AdminPage() {
name: customer.name || '',
location: customer.location || '',
email: customer.email || '',
phone: customer.phone || ''
phone: customer.phone || '',
password: '',
})
}}
>
@@ -566,18 +610,47 @@ export default function AdminPage() {
/>
</div>
</div>
<div className="col col-9">
<div className="col col-3">
<div className="form-group">
<label className="form-label">Portal-Passwort</label>
<input
type="password"
className="form-control"
value={customerForm.password}
onChange={(e) => setCustomerForm(prev => ({ ...prev, password: e.target.value }))}
placeholder="mind. 8 Zeichen"
autoComplete="new-password"
required
/>
</div>
</div>
<div className="col col-6">
<div className="form-group" style={{ marginTop: '24px' }}>
<p style={{ margin: '0 0 8px 0', fontSize: '13px', color: '#666' }}>
Der Kunde meldet sich damit unter{' '}
<a href="https://project.webklar.com/login.html" target="_blank" rel="noreferrer">
project.webklar.com
</a>{' '}
an (E-Mail + Passwort).
</p>
<button
className="btn btn-green"
onClick={async () => {
if (!customerForm.name) {
if (!customerForm.name?.trim()) {
alert('Bitte gib mindestens einen Namen ein.')
return
}
if (!customerForm.email?.trim()) {
alert('E-Mail ist für den Portal-Login erforderlich.')
return
}
if (!customerForm.password || customerForm.password.length < 8) {
alert('Portal-Passwort muss mindestens 8 Zeichen haben.')
return
}
const result = await createCustomer(customerForm)
if (result.success) {
setCustomerForm({ code: '', name: '', location: '', email: '', phone: '' })
setCustomerForm(emptyCustomerForm)
} else {
alert('Fehler beim Erstellen: ' + (result.error || 'Unbekannter Fehler'))
}