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 { useState, useEffect, useCallback } from 'react'
import { databases, DATABASE_ID, COLLECTIONS, ID, Query, isDemoMode } from '../lib/appwrite' import { databases, DATABASE_ID, COLLECTIONS, ID, Query, isDemoMode } from '../lib/appwrite'
import {
createCustomerWithPortalAccess,
updateCustomerWithPortalAccess,
} from '../lib/customerAdminApi'
const DEMO_MODE = isDemoMode const DEMO_MODE = isDemoMode
@@ -50,20 +54,18 @@ export function useCustomers() {
const createCustomer = async (data) => { const createCustomer = async (data) => {
if (DEMO_MODE) { 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]) setCustomers(prev => [...prev, newCustomer])
return { success: true, data: newCustomer } return { success: true, data: newCustomer }
} }
try { try {
const response = await databases.createDocument( const { password, ...fields } = data
DATABASE_ID, const result = await createCustomerWithPortalAccess({ ...fields, password })
COLLECTIONS.CUSTOMERS, const customer = result.customer
ID.unique(), setCustomers(prev => [...prev, customer])
data return { success: true, data: customer }
)
setCustomers(prev => [...prev, response])
return { success: true, data: response }
} catch (err) { } catch (err) {
return { success: false, error: err.message } return { success: false, error: err.message }
} }
@@ -71,19 +73,20 @@ export function useCustomers() {
const updateCustomer = async (id, data) => { const updateCustomer = async (id, data) => {
if (DEMO_MODE) { 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 } return { success: true }
} }
try { try {
const response = await databases.updateDocument( const { password, ...fields } = data
DATABASE_ID, const payload = { ...fields }
COLLECTIONS.CUSTOMERS, if (password) payload.password = password
id,
data const result = await updateCustomerWithPortalAccess(id, payload)
) const customer = result.customer
setCustomers(prev => prev.map(c => c.$id === id ? response : c)) setCustomers(prev => prev.map(c => c.$id === id ? customer : c))
return { success: true, data: response } return { success: true, data: customer }
} catch (err) { } catch (err) {
return { success: false, error: err.message } 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 [saving, setSaving] = useState(false)
const [saveMessage, setSaveMessage] = useState('') const [saveMessage, setSaveMessage] = useState('')
const [editingCustomer, setEditingCustomer] = useState(null) 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 [editingEmployee, setEditingEmployee] = useState(null)
const [employeeForm, setEmployeeForm] = useState({ userId: '', displayName: '', email: '', shortcode: '' }) const [employeeForm, setEmployeeForm] = useState({ userId: '', displayName: '', email: '', shortcode: '' })
const [adminSection, setAdminSection] = useState('config') const [adminSection, setAdminSection] = useState('config')
@@ -384,6 +399,7 @@ export default function AdminPage() {
<th>Location</th> <th>Location</th>
<th>Email</th> <th>Email</th>
<th>Phone</th> <th>Phone</th>
<th>Portal</th>
<th>Aktionen</th> <th>Aktionen</th>
</tr> </tr>
</thead> </thead>
@@ -433,14 +449,34 @@ export default function AdminPage() {
onChange={(e) => setCustomerForm(prev => ({ ...prev, phone: e.target.value }))} onChange={(e) => setCustomerForm(prev => ({ ...prev, phone: e.target.value }))}
/> />
</td> </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> <td>
<button <button
className="btn btn-green" className="btn btn-green"
onClick={async () => { 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) const result = await updateCustomer(customer.$id, customerForm)
if (result.success) { if (result.success) {
setEditingCustomer(null) 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" className="btn"
onClick={() => { onClick={() => {
setEditingCustomer(null) setEditingCustomer(null)
setCustomerForm({ code: '', name: '', location: '', email: '', phone: '' }) setCustomerForm(emptyCustomerForm)
}} }}
style={{ marginLeft: '4px' }} style={{ marginLeft: '4px' }}
> >
@@ -465,6 +501,13 @@ export default function AdminPage() {
<td>{customer.location || '-'}</td> <td>{customer.location || '-'}</td>
<td>{customer.email || '-'}</td> <td>{customer.email || '-'}</td>
<td>{customer.phone || '-'}</td> <td>{customer.phone || '-'}</td>
<td>
{customer.portalAccessEnabled ? (
<span style={{ color: '#059669', fontWeight: 600 }}>aktiv</span>
) : (
<span style={{ color: '#b45309' }}>inaktiv</span>
)}
</td>
<td> <td>
<button <button
className="btn" className="btn"
@@ -475,7 +518,8 @@ export default function AdminPage() {
name: customer.name || '', name: customer.name || '',
location: customer.location || '', location: customer.location || '',
email: customer.email || '', email: customer.email || '',
phone: customer.phone || '' phone: customer.phone || '',
password: '',
}) })
}} }}
> >
@@ -566,18 +610,47 @@ export default function AdminPage() {
/> />
</div> </div>
</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' }}> <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 <button
className="btn btn-green" className="btn btn-green"
onClick={async () => { onClick={async () => {
if (!customerForm.name) { if (!customerForm.name?.trim()) {
alert('Bitte gib mindestens einen Namen ein.') alert('Bitte gib mindestens einen Namen ein.')
return 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) const result = await createCustomer(customerForm)
if (result.success) { if (result.success) {
setCustomerForm({ code: '', name: '', location: '', email: '', phone: '' }) setCustomerForm(emptyCustomerForm)
} else { } else {
alert('Fehler beim Erstellen: ' + (result.error || 'Unbekannter Fehler')) alert('Fehler beim Erstellen: ' + (result.error || 'Unbekannter Fehler'))
} }