feat: add billing_interval to products supporting one-time, monthly and yearly subscription pricing

This commit is contained in:
DanielS
2026-05-01 02:33:51 +02:00
parent 1ec164cd7b
commit cc10352d72
5 changed files with 103 additions and 34 deletions

View File

@@ -53,6 +53,7 @@ const productSchema = z.object({
base_price: z.coerce.number().min(0, 'Preis darf nicht negativ sein'),
category_id: z.string().min(1, 'Bitte wählen Sie eine Kategorie'),
is_active: z.boolean().default(true),
billing_interval: z.enum(['one_time', 'monthly', 'yearly']).default('monthly'),
modules: z.array(moduleSchema).default([]),
})
@@ -69,6 +70,7 @@ export function CreateProductDialog({ children, categories, product }: { childre
base_price: product?.base_price || 0,
category_id: product?.category_id || '',
is_active: product?.is_active ?? true,
billing_interval: product?.billing_interval || 'monthly',
modules: product?.modules?.map(m => ({
name: m.name,
description: m.description || '',
@@ -121,7 +123,7 @@ export function CreateProductDialog({ children, categories, product }: { childre
<DialogTitle className="text-2xl font-bold">
{product ? 'Produkt bearbeiten' : 'Neues Produkt erstellen'}
</DialogTitle>
<DialogDescription className="text-muted-foreground">
<DialogDescription className="text-slate-300">
Definieren Sie das Basisprodukt und fügen Sie optionale oder erforderliche Module hinzu.
</DialogDescription>
</DialogHeader>
@@ -136,9 +138,9 @@ export function CreateProductDialog({ children, categories, product }: { childre
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Produktname</FormLabel>
<FormLabel className="text-white">Produktname</FormLabel>
<FormControl>
<Input placeholder="z.B. Basic ERP" className="bg-white/5 border-white/10" {...field} />
<Input placeholder="z.B. Basic ERP" className="bg-white/5 border-white/10 text-white" {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -149,9 +151,9 @@ export function CreateProductDialog({ children, categories, product }: { childre
name="base_price"
render={({ field }) => (
<FormItem>
<FormLabel>Basispreis ()</FormLabel>
<FormLabel className="text-white">Basispreis ()</FormLabel>
<FormControl>
<Input type="number" step="0.01" className="bg-white/5 border-white/10" {...field} />
<Input type="number" step="0.01" className="bg-white/5 border-white/10 text-white" {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -164,16 +166,16 @@ export function CreateProductDialog({ children, categories, product }: { childre
name="category_id"
render={({ field }) => (
<FormItem>
<FormLabel>Kategorie</FormLabel>
<FormLabel className="text-white">Kategorie</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger className="bg-white/5 border-white/10">
<SelectValue placeholder="Kategorie wählen" />
<SelectTrigger className="bg-white/5 border-white/10 text-white">
<SelectValue placeholder="Kategorie wählen" className="text-white" />
</SelectTrigger>
</FormControl>
<SelectContent className="glass-dark border-white/10 text-white">
{categories.map(cat => (
<SelectItem key={cat.id} value={cat.id}>{cat.name}</SelectItem>
<SelectItem key={cat.id} value={cat.id} className="text-white">{cat.name}</SelectItem>
))}
</SelectContent>
</Select>
@@ -182,14 +184,46 @@ export function CreateProductDialog({ children, categories, product }: { childre
)}
/>
<FormField
control={form.control}
name="billing_interval"
render={({ field }) => (
<FormItem>
<FormLabel className="text-white">Abrechnungsmodell</FormLabel>
<div className="grid grid-cols-3 gap-2 pt-1">
{[
{ value: 'one_time', label: 'Einmalig', icon: '💳' },
{ value: 'monthly', label: 'Monatlich', icon: '🔄' },
{ value: 'yearly', label: 'Jährlich', icon: '📅' },
].map(opt => (
<button
key={opt.value}
type="button"
onClick={() => field.onChange(opt.value)}
className={`flex flex-col items-center gap-1 p-3 rounded-xl border-2 text-sm font-medium transition-all ${
field.value === opt.value
? 'border-primary bg-primary/10 text-white'
: 'border-white/10 bg-white/5 text-slate-400 hover:bg-white/10'
}`}
>
<span className="text-lg">{opt.icon}</span>
{opt.label}
</button>
))}
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Beschreibung</FormLabel>
<FormLabel className="text-white">Beschreibung</FormLabel>
<FormControl>
<Input placeholder="Kurze Beschreibung des Produkts" className="bg-white/5 border-white/10" {...field} />
<Input placeholder="Kurze Beschreibung des Produkts" className="bg-white/5 border-white/10 text-white placeholder:text-slate-400" {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -233,9 +267,9 @@ export function CreateProductDialog({ children, categories, product }: { childre
name={`modules.${index}.name`}
render={({ field }) => (
<FormItem>
<FormLabel>Modulname</FormLabel>
<FormLabel className="text-white">Modulname</FormLabel>
<FormControl>
<Input placeholder="z.B. Cloud Storage" className="bg-white/10 border-white/10" {...field} />
<Input placeholder="z.B. Cloud Storage" className="bg-white/10 border-white/10 text-white placeholder:text-slate-400" {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -246,9 +280,9 @@ export function CreateProductDialog({ children, categories, product }: { childre
name={`modules.${index}.price`}
render={({ field }) => (
<FormItem>
<FormLabel>Aufpreis ()</FormLabel>
<FormLabel className="text-white">Aufpreis ()</FormLabel>
<FormControl>
<Input type="number" step="0.01" className="bg-white/10 border-white/10" {...field} />
<Input type="number" step="0.01" className="bg-white/10 border-white/10 text-white" {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -262,9 +296,9 @@ export function CreateProductDialog({ children, categories, product }: { childre
name={`modules.${index}.requirements`}
render={({ field }) => (
<FormItem>
<FormLabel>Benötigt (IDs, kommagetrennt)</FormLabel>
<FormLabel className="text-white">Benötigt (IDs, kommagetrennt)</FormLabel>
<FormControl>
<Input placeholder="UUIDs..." className="bg-white/10 border-white/10 text-xs" {...field} />
<Input placeholder="UUIDs..." className="bg-white/10 border-white/10 text-xs text-white" {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -275,9 +309,9 @@ export function CreateProductDialog({ children, categories, product }: { childre
name={`modules.${index}.exclusions`}
render={({ field }) => (
<FormItem>
<FormLabel>Schließt aus (IDs, kommagetrennt)</FormLabel>
<FormLabel className="text-white">Schließt aus (IDs, kommagetrennt)</FormLabel>
<FormControl>
<Input placeholder="UUIDs..." className="bg-white/10 border-white/10 text-xs" {...field} />
<Input placeholder="UUIDs..." className="bg-white/10 border-white/10 text-xs text-white" {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -298,7 +332,7 @@ export function CreateProductDialog({ children, categories, product }: { childre
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>Erforderlich</FormLabel>
<FormLabel className="text-white">Erforderlich</FormLabel>
</div>
</FormItem>
)}
@@ -308,7 +342,7 @@ export function CreateProductDialog({ children, categories, product }: { childre
))}
{fields.length === 0 && (
<div className="text-center py-6 border-2 border-dashed border-white/5 rounded-lg text-muted-foreground text-sm">
<div className="text-center py-6 border-2 border-dashed border-white/5 rounded-lg text-slate-400 text-sm">
Noch keine Module hinzugefügt.
</div>
)}