Files
Dimension-47/client/src/features/campaigns/components/create-campaign-modal.tsx
Alexander Zielonka 090aae53d8 Initial commit: Dimension47 project setup
- NestJS backend with JWT auth, Prisma ORM, Swagger docs
- Vite + React 19 frontend with TypeScript
- Tailwind CSS v4 with custom dark theme design system
- Auth module: Login, Register, Protected routes
- Campaigns module: CRUD, Member management
- Full Prisma schema for PF2e campaign management
- Docker Compose for PostgreSQL

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 16:24:18 +01:00

112 lines
3.6 KiB
TypeScript

import { useState } from 'react';
import { X } from 'lucide-react';
import { Button, Input, Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/shared/components/ui';
import { api } from '@/shared/lib/api';
interface CreateCampaignModalProps {
onClose: () => void;
onCreated: () => void;
}
export function CreateCampaignModal({ onClose, onCreated }: CreateCampaignModalProps) {
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
if (!name.trim()) {
setError('Bitte einen Namen eingeben');
return;
}
setIsLoading(true);
try {
await api.createCampaign({
name: name.trim(),
description: description.trim() || undefined,
});
onCreated();
} catch (err) {
console.error('Failed to create campaign:', err);
setError('Kampagne konnte nicht erstellt werden');
} finally {
setIsLoading(false);
}
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
onClick={onClose}
/>
{/* Modal */}
<Card className="relative z-10 w-full max-w-md mx-4 animate-slide-up">
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>Neue Kampagne</CardTitle>
<button
onClick={onClose}
className="h-8 w-8 rounded-lg hover:bg-bg-tertiary flex items-center justify-center transition-colors"
>
<X className="h-4 w-4 text-text-secondary" />
</button>
</CardHeader>
<form onSubmit={handleSubmit}>
<CardContent className="space-y-4">
{error && (
<div className="p-3 rounded-lg bg-error-500/10 border border-error-500/20 text-error-500 text-sm">
{error}
</div>
)}
<Input
label="Name"
type="text"
placeholder="z.B. Rise of the Runelords"
value={name}
onChange={(e) => setName(e.target.value)}
disabled={isLoading}
autoFocus
/>
<div>
<label className="block text-sm font-medium text-text-secondary mb-1.5">
Beschreibung (optional)
</label>
<textarea
className="flex min-h-[100px] w-full rounded-lg border border-border bg-bg-secondary px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 disabled:cursor-not-allowed disabled:opacity-50 transition-colors resize-none"
placeholder="Eine kurze Beschreibung der Kampagne..."
value={description}
onChange={(e) => setDescription(e.target.value)}
disabled={isLoading}
/>
</div>
</CardContent>
<CardFooter className="gap-3">
<Button
type="button"
variant="outline"
onClick={onClose}
disabled={isLoading}
className="flex-1"
>
Abbrechen
</Button>
<Button
type="submit"
isLoading={isLoading}
className="flex-1"
>
Erstellen
</Button>
</CardFooter>
</form>
</Card>
</div>
);
}