feat: all 4 UX priorities — wizard, examples, onboarding, pipeline

- Offer wizard: 4→3 steps by merging 'Prüfen' into PDF review step
- Search mask: quick-select chips for area ranges, budget, and city presets
- Onboarding: WelcomeDialog (3 slides, localStorage) on first visit
- Deal Pipeline: Kanban board (6 stages, 8 mock items, advance-to-next-stage)
  added to demand workspace navigation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-18 22:09:06 +02:00
parent 9cf3e72033
commit 14badd1ca8
9 changed files with 581 additions and 19 deletions
+188
View File
@@ -0,0 +1,188 @@
import { useState } from 'react'
import {
Dialog,
DialogContent,
Box,
Typography,
MobileStepper,
Button,
} from '@mui/material'
import {
Building2,
CheckCircle2,
Zap,
Search,
Mic,
Sparkles,
Newspaper,
FileCheck,
ArrowLeft,
ArrowRight,
} from 'lucide-react'
function FeatureRow({
icon,
color,
text,
}: {
icon: React.ReactNode
color: string
text: string
}) {
return (
<Box className="flex items-center gap-2 mt-2">
<Box sx={{ color, flexShrink: 0 }}>{icon}</Box>
<Typography variant="body2">{text}</Typography>
</Box>
)
}
function Slide0() {
return (
<Box className="flex flex-col items-center text-center px-2">
<Box className="mb-4">
<Building2 size={48} color="#1e3a5f" />
</Box>
<Typography variant="h5" sx={{ fontWeight: 700, mb: 1 }}>
Willkommen bei Property Match
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
Die KI-gestützte Plattform für gewerbliche Immobilien
</Typography>
<Box className="w-full text-left">
<FeatureRow
icon={<CheckCircle2 size={18} />}
color="#1a7a4a"
text="Verifizierte Portfolio-Objekte"
/>
<FeatureRow
icon={<CheckCircle2 size={18} />}
color="#1a7a4a"
text="Externer Markt & Inserate"
/>
<FeatureRow
icon={<Zap size={18} />}
color="#7c3aed"
text="Schattenmarkt-Signale der KI"
/>
</Box>
</Box>
)
}
function Slide1() {
return (
<Box className="flex flex-col items-center text-center px-2">
<Box className="mb-4">
<Search size={48} color="#1e3a5f" />
</Box>
<Typography variant="h5" sx={{ fontWeight: 700, mb: 1 }}>
Bedarf beschreiben KI findet Matches
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
Schreiben Sie Ihren Flächenbedarf in natürlicher Sprache. Die KI extrahiert alle Kriterien
und findet die besten Matches aus Portfolio, Markt und Schattenmarkt.
</Typography>
<Box className="w-full text-left">
<FeatureRow
icon={<Mic size={18} />}
color="#d97706"
text="Spracheingabe auf Deutsch"
/>
<FeatureRow
icon={<Sparkles size={18} />}
color="#7c3aed"
text="KI analysiert und gewichtet automatisch"
/>
</Box>
</Box>
)
}
function Slide2() {
return (
<Box className="flex flex-col items-center text-center px-2">
<Box className="mb-4">
<Zap size={48} color="#7c3aed" />
</Box>
<Typography variant="h5" sx={{ fontWeight: 700, mb: 1 }}>
Schattenmarkt Vor dem Markt informiert
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
Die KI überwacht Stelleninserate, Pressemeldungen und Baubewilligungen frühzeitige
Hinweise auf mögliche Verfügbarkeiten, bevor sie ausgeschrieben werden.
</Typography>
<Box className="w-full text-left">
<FeatureRow
icon={<Newspaper size={18} />}
color="#d97706"
text="Presse & Stelleninserate"
/>
<FeatureRow
icon={<FileCheck size={18} />}
color="#1a7a4a"
text="Baubewilligungen & Berichte"
/>
</Box>
</Box>
)
}
const SLIDES = [<Slide0 />, <Slide1 />, <Slide2 />]
export function WelcomeDialog() {
const [open, setOpen] = useState(() => {
return localStorage.getItem('property_match_welcomed') === null
})
const [activeStep, setActiveStep] = useState(0)
function dismiss() {
localStorage.setItem('property_match_welcomed', '1')
setOpen(false)
}
return (
<Dialog
open={open}
maxWidth="sm"
fullWidth
disableEscapeKeyDown
>
<DialogContent sx={{ pt: 4, pb: 2 }}>
{SLIDES[activeStep]}
<MobileStepper
variant="dots"
steps={3}
position="static"
activeStep={activeStep}
sx={{ mt: 3, background: 'transparent' }}
backButton={
<Button
size="small"
onClick={() => setActiveStep((s) => s - 1)}
disabled={activeStep === 0}
startIcon={<ArrowLeft size={16} />}
>
Zurück
</Button>
}
nextButton={
activeStep === 2 ? (
<Button size="small" variant="contained" onClick={dismiss}>
Los geht's
</Button>
) : (
<Button
size="small"
onClick={() => setActiveStep((s) => s + 1)}
endIcon={<ArrowRight size={16} />}
>
Weiter
</Button>
)
}
/>
</DialogContent>
</Dialog>
)
}