Compare commits
2 Commits
883b607482
...
7346b42ff3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7346b42ff3 | ||
|
|
bc02a6d000 |
90
shop/app/not-found.tsx
Normal file
90
shop/app/not-found.tsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import { AsciiShaderBackground } from '@/components/AsciiShaderBackground'
|
||||||
|
import { ArrowLeft, Home } from 'lucide-react'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-transparent text-slate-100 overflow-hidden relative selection:bg-blue-500/30 selection:text-blue-200">
|
||||||
|
<AsciiShaderBackground />
|
||||||
|
|
||||||
|
<div className="relative z-10 flex flex-col items-center justify-center min-h-screen px-4">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, ease: [0.25, 0.1, 0.25, 1] }}
|
||||||
|
className="text-center space-y-8 max-w-lg"
|
||||||
|
>
|
||||||
|
{/* Große 404 */}
|
||||||
|
<motion.h1
|
||||||
|
initial={{ opacity: 0, scale: 0.8 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ duration: 0.8, delay: 0.1, type: 'spring', stiffness: 100 }}
|
||||||
|
className="text-[10rem] md:text-[14rem] font-black leading-none tracking-tighter bg-gradient-to-b from-white via-slate-300 to-slate-600 bg-clip-text text-transparent select-none"
|
||||||
|
>
|
||||||
|
404
|
||||||
|
</motion.h1>
|
||||||
|
|
||||||
|
{/* Beschreibung */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.3 }}
|
||||||
|
className="space-y-3"
|
||||||
|
>
|
||||||
|
<h2 className="text-2xl md:text-3xl font-bold text-white">
|
||||||
|
Seite nicht gefunden
|
||||||
|
</h2>
|
||||||
|
<p className="text-slate-400 text-sm md:text-base leading-relaxed max-w-md mx-auto">
|
||||||
|
Die angeforderte Seite existiert nicht oder wurde verschoben.
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Buttons */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.5 }}
|
||||||
|
className="flex flex-col sm:flex-row items-center justify-center gap-3 pt-4"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
size="lg"
|
||||||
|
className="h-12 px-8 text-base gap-2 bg-gradient-to-r from-blue-600 to-sky-500 hover:from-blue-500 hover:to-sky-400 border-0 shadow-[0_0_20px_rgba(56,189,248,0.25)] hover:shadow-[0_0_30px_rgba(56,189,248,0.4)] transition-all"
|
||||||
|
>
|
||||||
|
<Link href="/">
|
||||||
|
<Home className="w-4 h-4" />
|
||||||
|
Zur Startseite
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
variant="ghost"
|
||||||
|
size="lg"
|
||||||
|
className="h-12 px-8 text-base gap-2 text-slate-400 hover:text-white border border-white/10 hover:border-white/20 hover:bg-white/5"
|
||||||
|
onClick={() => typeof window !== 'undefined' && window.history.back()}
|
||||||
|
>
|
||||||
|
<button type="button">
|
||||||
|
<ArrowLeft className="w-4 h-4" />
|
||||||
|
Zurück
|
||||||
|
</button>
|
||||||
|
</Button>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Subtle info */}
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.7 }}
|
||||||
|
className="text-xs text-slate-600 pt-8"
|
||||||
|
>
|
||||||
|
Fehlercode 404 · CASPOS Shop
|
||||||
|
</motion.p>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -939,38 +939,38 @@ export function OrderWizard({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Left Sidebar Status Stepper */}
|
{/* Left Sidebar Status Stepper (matching ProgressStepper sky-blue design) */}
|
||||||
<div className="space-y-2 py-4 border-t border-b border-white/5">
|
<div className="space-y-2 py-4 border-t border-b border-white/5">
|
||||||
<p className="text-xs font-bold uppercase tracking-wider text-slate-500">
|
<p className="text-xs font-bold uppercase tracking-wider text-slate-500">
|
||||||
Status
|
Status
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-row flex-wrap items-center gap-x-3 gap-y-2 pl-1">
|
<div className="flex flex-row flex-wrap items-center gap-x-3 gap-y-2 pl-1">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<div className="w-5 h-5 rounded-full bg-green-500/20 border border-green-500/35 flex items-center justify-center text-green-400 text-[10px]">
|
<div className="w-6 h-6 rounded-full bg-sky-500 border-2 border-sky-400 flex items-center justify-center shadow-[0_0_10px_rgba(56,189,248,0.4)]">
|
||||||
<Check className="w-3 h-3" />
|
<Check className="w-3.5 h-3.5 text-slate-950 stroke-[3]" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-slate-400 font-medium">Kunde</span>
|
<span className="text-xs text-sky-300 font-semibold">Kunde</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-slate-700 text-xs">/</span>
|
<div className="w-6 h-[2px] bg-gradient-to-r from-sky-500 to-sky-400 rounded-full shadow-[0_0_6px_rgba(56,189,248,0.5)]" />
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<div className="w-5 h-5 rounded-full bg-green-500/20 border border-green-500/35 flex items-center justify-center text-green-400 text-[10px]">
|
<div className="w-6 h-6 rounded-full bg-sky-500 border-2 border-sky-400 flex items-center justify-center shadow-[0_0_10px_rgba(56,189,248,0.4)]">
|
||||||
<Check className="w-3 h-3" />
|
<Check className="w-3.5 h-3.5 text-slate-950 stroke-[3]" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-slate-400 font-medium">Modell</span>
|
<span className="text-xs text-sky-300 font-semibold">Modell</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-slate-700 text-xs">/</span>
|
<div className="w-6 h-[2px] bg-gradient-to-r from-sky-500 to-sky-400 rounded-full shadow-[0_0_6px_rgba(56,189,248,0.5)]" />
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<div className="w-5 h-5 rounded-full bg-primary/20 border border-primary text-primary flex items-center justify-center text-[10px] font-bold shadow-[0_0_10px_rgba(59,130,246,0.2)]">
|
<div className="w-6 h-6 rounded-full bg-slate-950 border-2 border-sky-400 text-sky-400 flex items-center justify-center text-xs font-bold shadow-[0_0_15px_rgba(56,189,248,0.5)]">
|
||||||
3
|
3
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-white font-bold">Software</span>
|
<span className="text-xs text-sky-300 font-bold">Software</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-slate-700 text-xs">/</span>
|
<div className="w-6 h-[2px] bg-slate-800 rounded-full" />
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<div className="w-5 h-5 rounded-full bg-white/5 border border-white/10 text-slate-500 flex items-center justify-center text-[10px]">
|
<div className="w-6 h-6 rounded-full bg-slate-900 border-2 border-slate-800 text-slate-500 flex items-center justify-center text-xs">
|
||||||
4
|
4
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-slate-600 font-medium">Abschluss</span>
|
<span className="text-xs text-slate-500 font-medium">Abschluss</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -81,17 +81,26 @@ export function SummarySidebar({
|
|||||||
prevStep,
|
prevStep,
|
||||||
}: SummarySidebarProps) {
|
}: SummarySidebarProps) {
|
||||||
const [isOpen, setIsOpen] = React.useState(false)
|
const [isOpen, setIsOpen] = React.useState(false)
|
||||||
|
const [selectedBasketIdx, setSelectedBasketIdx] = React.useState<number | null>(null)
|
||||||
|
|
||||||
|
// When editing a basket item via pencil, open the modal for that item
|
||||||
|
const handleEditBasketItem = (idx: number) => {
|
||||||
|
editBasketItem(idx)
|
||||||
|
setIsOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fmt = (val: number) => new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(val)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
<Card className="bg-[oklch(0.145_0.01_148)]/90 backdrop-blur-md border border-slate-800 shadow-[0_0_30px_rgba(0,0,0,0.5)] rounded-2xl overflow-hidden flex flex-col h-full">
|
<Card className="bg-[oklch(0.145_0.01_148)]/90 backdrop-blur-md border border-slate-800 shadow-[0_0_30px_rgba(0,0,0,0.5)] rounded-2xl overflow-hidden flex flex-col h-full">
|
||||||
<CardHeader className="shrink-0">
|
<CardHeader className="shrink-0 pb-3">
|
||||||
<CardTitle className="text-white">Zusammenfassung</CardTitle>
|
<CardTitle className="text-white text-lg">Zusammenfassung</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-5 flex-1 flex flex-col overflow-hidden space-y-4">
|
<CardContent className="p-5 pt-0 flex-1 flex flex-col overflow-hidden gap-3">
|
||||||
|
|
||||||
{/* Active Selections & Price Calculation (Fixed) */}
|
{/* Active Selections & Price - scrollable */}
|
||||||
<div className="space-y-4 shrink-0 overflow-y-auto max-h-[40%] pr-1 scrollbar-thin">
|
<div className="space-y-3 flex-1 overflow-y-auto pr-1 scrollbar-thin scrollbar-thumb-white/10 scrollbar-track-transparent">
|
||||||
{visibleCategories.map(cat => {
|
{visibleCategories.map(cat => {
|
||||||
const sel = selections[cat.id]
|
const sel = selections[cat.id]
|
||||||
const selectedProds: Product[] = []
|
const selectedProds: Product[] = []
|
||||||
@@ -106,13 +115,13 @@ export function SummarySidebar({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (selectedProds.length === 0) return (
|
if (selectedProds.length === 0) return (
|
||||||
<div key={cat.id} className="text-xs text-slate-500 italic flex items-center gap-1">
|
<div key={cat.id} className="text-xs text-slate-500 italic flex items-center gap-1.5">
|
||||||
{cat.is_required ? (
|
{cat.is_required ? (
|
||||||
<AlertCircle className="w-3 h-3 text-destructive" />
|
<AlertCircle className="w-3 h-3 text-destructive shrink-0" />
|
||||||
) : (
|
) : (
|
||||||
<span className="w-3 h-3 inline-block" />
|
<span className="w-3 h-3 inline-block shrink-0" />
|
||||||
)}
|
)}
|
||||||
{cat.name}: {cat.is_required ? 'nicht gewählt' : 'nicht gewählt (optional)'}
|
<span>{cat.name}: {cat.is_required ? 'nicht gewählt' : 'nicht gewählt (optional)'}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -121,9 +130,7 @@ export function SummarySidebar({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={cat.id} className="space-y-1">
|
<div key={cat.id} className="space-y-1">
|
||||||
<div className="flex justify-between text-sm">
|
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wider">{cat.name}</p>
|
||||||
<span className="text-slate-400 font-medium">{cat.name}</span>
|
|
||||||
</div>
|
|
||||||
{sortedProds.map((prod, idx) => {
|
{sortedProds.map((prod, idx) => {
|
||||||
const isFree = idx < freeLimit
|
const isFree = idx < freeLimit
|
||||||
const actualPrice = isFree ? 0 : prod.base_price
|
const actualPrice = isFree ? 0 : prod.base_price
|
||||||
@@ -131,8 +138,8 @@ export function SummarySidebar({
|
|||||||
<div key={prod.id} className="space-y-0.5 pl-2">
|
<div key={prod.id} className="space-y-0.5 pl-2">
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-white">{prod.name} {isFree && <span className="text-[10px] text-green-400">(Frei)</span>}</span>
|
<span className="text-white">{prod.name} {isFree && <span className="text-[10px] text-green-400">(Frei)</span>}</span>
|
||||||
<span className="text-white">
|
<span className="text-white tabular-nums">
|
||||||
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(actualPrice)}
|
{fmt(actualPrice)}
|
||||||
{' '}<span className="text-slate-400 text-xs">{billingLabel(prod.billing_interval)}</span>
|
{' '}<span className="text-slate-400 text-xs">{billingLabel(prod.billing_interval)}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,8 +149,8 @@ export function SummarySidebar({
|
|||||||
return mod ? (
|
return mod ? (
|
||||||
<div key={mId} className="flex justify-between text-xs pl-2">
|
<div key={mId} className="flex justify-between text-xs pl-2">
|
||||||
<span className="text-slate-300">+ {mod.name} {mod.has_quantity ? `(x${qty})` : ''}</span>
|
<span className="text-slate-300">+ {mod.name} {mod.has_quantity ? `(x${qty})` : ''}</span>
|
||||||
<span className="text-slate-300">
|
<span className="text-slate-300 tabular-nums">
|
||||||
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(mod.price * qty)}
|
{fmt(mod.price * qty)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : null
|
) : null
|
||||||
@@ -154,6 +161,8 @@ export function SummarySidebar({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{/* Pricing block */}
|
||||||
<Separator className="bg-white/10" />
|
<Separator className="bg-white/10" />
|
||||||
{oneTimeTotal > 0 && monthlyTotal > 0 ? (
|
{oneTimeTotal > 0 && monthlyTotal > 0 ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -161,30 +170,30 @@ export function SummarySidebar({
|
|||||||
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wider">Einmalig:</p>
|
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wider">Einmalig:</p>
|
||||||
<div className="flex justify-between text-xs text-slate-400">
|
<div className="flex justify-between text-xs text-slate-400">
|
||||||
<span>Netto:</span>
|
<span>Netto:</span>
|
||||||
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(oneTimeNet)}</span>
|
<span className="tabular-nums">{fmt(oneTimeNet)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-xs text-slate-400">
|
<div className="flex justify-between text-xs text-slate-400">
|
||||||
<span>zzgl. 19% MwSt.:</span>
|
<span>zzgl. 19% MwSt.:</span>
|
||||||
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(oneTimeTax)}</span>
|
<span className="tabular-nums">{fmt(oneTimeTax)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-sm font-bold text-white">
|
<div className="flex justify-between text-sm font-bold text-white">
|
||||||
<span>Gesamt (brutto):</span>
|
<span>Gesamt (brutto):</span>
|
||||||
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(oneTimeGross)}</span>
|
<span className="tabular-nums">{fmt(oneTimeGross)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1 pt-2 border-t border-white/10">
|
<div className="space-y-1 pt-2 border-t border-white/10">
|
||||||
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wider">Monatlich:</p>
|
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wider">Monatlich:</p>
|
||||||
<div className="flex justify-between text-xs text-slate-400">
|
<div className="flex justify-between text-xs text-slate-400">
|
||||||
<span>Netto:</span>
|
<span>Netto:</span>
|
||||||
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(monthlyNet)} / mtl.</span>
|
<span className="tabular-nums">{fmt(monthlyNet)} / mtl.</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-xs text-slate-400">
|
<div className="flex justify-between text-xs text-slate-400">
|
||||||
<span>zzgl. 19% MwSt.:</span>
|
<span>zzgl. 19% MwSt.:</span>
|
||||||
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(monthlyTax)} / mtl.</span>
|
<span className="tabular-nums">{fmt(monthlyTax)} / mtl.</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-sm font-bold text-white">
|
<div className="flex justify-between text-sm font-bold text-white">
|
||||||
<span>Gesamt (brutto):</span>
|
<span>Gesamt (brutto):</span>
|
||||||
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(monthlyGross)} / mtl.</span>
|
<span className="tabular-nums">{fmt(monthlyGross)} / mtl.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -192,30 +201,30 @@ export function SummarySidebar({
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex justify-between text-xs text-slate-400">
|
<div className="flex justify-between text-xs text-slate-400">
|
||||||
<span>Netto-Gesamtbetrag:</span>
|
<span>Netto-Gesamtbetrag:</span>
|
||||||
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(oneTimeNet)}</span>
|
<span className="tabular-nums">{fmt(oneTimeNet)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-xs text-slate-400">
|
<div className="flex justify-between text-xs text-slate-400">
|
||||||
<span>zzgl. 19% MwSt.:</span>
|
<span>zzgl. 19% MwSt.:</span>
|
||||||
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(oneTimeTax)}</span>
|
<span className="tabular-nums">{fmt(oneTimeTax)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-base font-bold text-blue-500 dark:text-blue-400">
|
<div className="flex justify-between text-base font-bold text-sky-400">
|
||||||
<span>Gesamtbetrag (brutto):</span>
|
<span>Gesamtbetrag (brutto):</span>
|
||||||
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(oneTimeGross)}</span>
|
<span className="tabular-nums">{fmt(oneTimeGross)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex justify-between text-xs text-slate-400">
|
<div className="flex justify-between text-xs text-slate-400">
|
||||||
<span>Netto-Gesamtbetrag:</span>
|
<span>Netto-Gesamtbetrag:</span>
|
||||||
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(monthlyNet)} / mtl.</span>
|
<span className="tabular-nums">{fmt(monthlyNet)} / mtl.</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-xs text-slate-400">
|
<div className="flex justify-between text-xs text-slate-400">
|
||||||
<span>zzgl. 19% MwSt.:</span>
|
<span>zzgl. 19% MwSt.:</span>
|
||||||
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(monthlyTax)} / mtl.</span>
|
<span className="tabular-nums">{fmt(monthlyTax)} / mtl.</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-base font-bold text-blue-500 dark:text-blue-400">
|
<div className="flex justify-between text-base font-bold text-sky-400">
|
||||||
<span>Gesamtbetrag (brutto):</span>
|
<span>Gesamtbetrag (brutto):</span>
|
||||||
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(monthlyGross)} / mtl.</span>
|
<span className="tabular-nums">{fmt(monthlyGross)} / mtl.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -242,48 +251,66 @@ export function SummarySidebar({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Warenkorb List (Scrollable) */}
|
{/* Kassen-Liste unten (selectable) */}
|
||||||
{basketItems.length > 0 && (
|
{basketItems.length > 0 && (
|
||||||
<div className="flex-1 flex flex-col min-h-0 pt-3 border-t border-white/10 space-y-2 overflow-hidden">
|
<div className="shrink-0 pt-3 border-t border-white/10 space-y-2">
|
||||||
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wider shrink-0">Warenkorb ({basketItems.length}):</p>
|
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wider">Kassen ({basketItems.length}):</p>
|
||||||
<div className="flex-1 overflow-y-auto pr-1 space-y-1.5 subpixel-antialiased scrollbar-thin scrollbar-thumb-white/10 scrollbar-track-transparent">
|
<div className="flex flex-wrap gap-1.5 max-h-28 overflow-y-auto pr-1 scrollbar-thin scrollbar-thumb-white/10 scrollbar-track-transparent">
|
||||||
{basketItems.map((item, idx) => (
|
{basketItems.map((item, idx) => (
|
||||||
<div key={idx} className={`flex justify-between items-center text-xs bg-white/5 p-2 rounded border transition-all duration-300 ${idx === editingIdx ? 'border-blue-500/50 shadow-[0_0_10px_rgba(59,130,246,0.3)]' : 'border-white/10'}`}>
|
<button
|
||||||
<div className="flex flex-col">
|
key={idx}
|
||||||
<span className="text-white font-medium">{item.deviceName}</span>
|
onClick={() => setSelectedBasketIdx(selectedBasketIdx === idx ? null : idx)}
|
||||||
<span className="text-[10px] text-slate-400 capitalize">
|
className={`flex items-center gap-2 text-xs px-3 py-2 rounded-lg border transition-all duration-200 text-left group ${
|
||||||
{item.billingInterval === 'one_time' ? 'Kauf' : 'Abo'}
|
selectedBasketIdx === idx
|
||||||
{item.licenseNumber ? ` · Lizenz: ${item.licenseNumber}` : ''}
|
? 'bg-sky-500/15 border-sky-500/40 text-white shadow-[0_0_10px_rgba(56,189,248,0.15)]'
|
||||||
</span>
|
: idx === editingIdx
|
||||||
</div>
|
? 'bg-blue-500/10 border-blue-500/30 text-white'
|
||||||
<div className="flex items-center gap-1.5">
|
: 'bg-white/5 border-white/10 text-slate-400 hover:bg-white/10 hover:text-white'
|
||||||
<Button
|
}`}
|
||||||
type="button"
|
>
|
||||||
variant="ghost"
|
<span className="font-medium truncate max-w-[120px]">{item.deviceName}</span>
|
||||||
size="icon"
|
{item.licenseNumber && (
|
||||||
className="w-7 h-7 hover:bg-white/10 text-slate-400 hover:text-white"
|
<span className="text-[10px] text-slate-500 truncate max-w-[80px]">{item.licenseNumber}</span>
|
||||||
onClick={() => {
|
)}
|
||||||
editBasketItem(idx)
|
</button>
|
||||||
setIsOpen(true)
|
|
||||||
}}
|
|
||||||
title="Kasse bearbeiten"
|
|
||||||
>
|
|
||||||
<Pencil className="w-3.5 h-3.5" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="w-7 h-7 hover:bg-white/10 text-destructive hover:text-red-400"
|
|
||||||
onClick={() => deleteBasketItem(idx)}
|
|
||||||
title="Kasse löschen"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-3.5 h-3.5" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Selected basket item actions */}
|
||||||
|
{selectedBasketIdx !== null && basketItems[selectedBasketIdx] && (
|
||||||
|
<div className="flex items-center gap-2 pt-1">
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="text-xs text-white font-medium truncate">{basketItems[selectedBasketIdx].deviceName}</p>
|
||||||
|
<p className="text-[10px] text-slate-500 capitalize">
|
||||||
|
{basketItems[selectedBasketIdx].billingInterval === 'one_time' ? 'Kauf' : 'Abo'}
|
||||||
|
{basketItems[selectedBasketIdx].licenseNumber ? ` · ${basketItems[selectedBasketIdx].licenseNumber}` : ''}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="w-7 h-7 hover:bg-sky-500/20 text-sky-400 hover:text-sky-300"
|
||||||
|
onClick={() => handleEditBasketItem(selectedBasketIdx)}
|
||||||
|
title="Name / Lizenz bearbeiten"
|
||||||
|
>
|
||||||
|
<Pencil className="w-3.5 h-3.5" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="w-7 h-7 hover:bg-red-500/20 text-red-400 hover:text-red-300"
|
||||||
|
onClick={() => {
|
||||||
|
deleteBasketItem(selectedBasketIdx)
|
||||||
|
setSelectedBasketIdx(null)
|
||||||
|
}}
|
||||||
|
title="Kasse löschen"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-3.5 h-3.5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -291,7 +318,7 @@ export function SummarySidebar({
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full border-primary/30 text-white hover:bg-primary/10 gap-2 h-10 text-sm"
|
className="w-full border-sky-500/30 text-white hover:bg-sky-500/10 gap-2 h-10 text-sm"
|
||||||
onClick={() => setIsOpen(true)}
|
onClick={() => setIsOpen(true)}
|
||||||
disabled={isNextStepDisabled}
|
disabled={isNextStepDisabled}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user