Kundenseite: Kundenansicht oeffnen, letzter Login, Preview per Einmal-Link

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-01 19:44:50 +00:00
1 parent 370724a9a7
commit fce2f41463
7 files changed
+92 -7

No files matched your search

+20 -4
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react'
import { useParams, Link, useNavigate, useSearchParams } from 'react-router-dom'
import { FaSpinner, FaStar, FaArrowLeft, FaFloppyDisk, FaArrowUp, FaKey, FaArrowUpRightFromSquare, FaBoxArchive, FaBoxOpen, FaTrash } from 'react-icons/fa6'
import { FaSpinner, FaStar, FaArrowLeft, FaFloppyDisk, FaArrowUp, FaKey, FaBoxArchive, FaBoxOpen, FaTrash } from 'react-icons/fa6'
import { databases, DATABASE_ID, COLLECTIONS, Query } from '../lib/appwrite'
import { PageHeader, Card, Button, Badge, Tabs, EmptyState, Field } from '../components/ui'
import { StatusPill, UrgentPill } from '../components/ui/StatusPill'
@@ -13,6 +13,9 @@ import { useWebsiteProjects } from '../hooks/useWebsiteProjects'
import { customerTabs, resolveCustomerTab } from '../lib/customerTabs'
import { customerStage, isArchived, isInvoiceReady, setCustomerArchived, upgradeToCustomer } from '../lib/leads'
import CustomerViewLog from '../components/customer/CustomerViewLog'
import KundenansichtButton from '../components/customer/KundenansichtButton'
import { getCustomerPortalStatus } from '../lib/teamApi'
import { formatLastLogin } from '../lib/lastLogin'
const PORTAL_URL = 'https://project.webklar.com'
@@ -85,6 +88,7 @@ export default function CustomerDetailPage() {
subtitle={[customer.location, customer.email, customer.phone].filter(Boolean).join(' · ')}
actions={
<>
<KundenansichtButton customer={customer} />
<Button variant="ghost" onClick={toggleArchived}>
{archived ? <><FaBoxOpen /> Wiederherstellen</> : <><FaBoxArchive /> Archivieren</>}
</Button>
@@ -200,6 +204,15 @@ function PortalAccessCard({ customer, onSaved }) {
const hasAccess = Boolean(customer.portalAccessEnabled && customer.appwriteUserId)
const currentPassword = getCustomerPortalPassword(customer)
// Letzter Login kommt vom Portal (customerPortalAccess), nicht aus dem Kunden-Dokument
const [lastLogin, setLastLogin] = useState(undefined)
useEffect(() => {
let active = true
getCustomerPortalStatus(customer.$id)
.then((status) => active && setLastLogin(status.lastLoginAt || null))
.catch(() => active && setLastLogin(false))
return () => { active = false }
}, [customer.$id])
const savePassword = async () => {
if (!customer.email) {
@@ -232,6 +245,11 @@ function PortalAccessCard({ customer, onSaved }) {
: <Badge tone="muted">kein Zugang</Badge>}
</div>
<div className="flex items-center justify-between" style={{ marginBottom: 12 }}>
<span className="muted">Letzter Login</span>
<span>{lastLogin === undefined ? '…' : lastLogin === false ? 'nicht abrufbar' : formatLastLogin(lastLogin)}</span>
</div>
<Field label="Login (E-Mail)">
<CopyableCredential value={customer.email} label="Login" />
</Field>
@@ -254,9 +272,7 @@ function PortalAccessCard({ customer, onSaved }) {
<Button variant="primary" onClick={savePassword} disabled={busy}>
{busy ? <FaSpinner className="spinner" /> : <><FaKey /> {hasAccess ? 'Passwort ändern' : 'Zugang anlegen'}</>}
</Button>
<Button as="a" href={PORTAL_URL} target="_blank" rel="noreferrer" variant="ghost">
<FaArrowUpRightFromSquare /> Portal öffnen
</Button>
<KundenansichtButton customer={customer} variant="ghost" />
{msg && <span className="muted">{msg}</span>}
</div>
<p className="muted" style={{ marginTop: 8, fontSize: 13 }}>
+1 -1
View File
@@ -574,7 +574,7 @@ export default function ProjectsPage() {
<FaGlobe style={hostBusyId === project.$id ? { animation: 'spin 1s linear infinite' } : undefined} />
</button>
) : (
<PreviewLinkButton href={project.previewUrl} />
<PreviewLinkButton href={project.previewUrl} subdomain={project.subdomain} />
)}
{hosted && ready && !project.archived && (
<button