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:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user