feat(17-03): create CreateInitiativeDialog with shadcn primitives

- Install shadcn Dialog, Input, Label, Textarea components
- Move from @/ literal dir to src/components/ui/ (known shadcn CLI issue)
- CreateInitiativeDialog: controlled dialog with name/description fields
- tRPC createInitiative mutation with loading/error states
- Form resets on open, validates name non-empty
This commit is contained in:
Lukas May
2026-02-04 21:04:05 +01:00
parent ec93835ae5
commit e5acb9f214
7 changed files with 410 additions and 29 deletions

View File

@@ -0,0 +1,115 @@
import { useEffect, useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { trpc } from "@/lib/trpc";
interface CreateInitiativeDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export function CreateInitiativeDialog({
open,
onOpenChange,
}: CreateInitiativeDialogProps) {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [error, setError] = useState<string | null>(null);
const utils = trpc.useUtils();
const createMutation = trpc.createInitiative.useMutation({
onSuccess: () => {
utils.listInitiatives.invalidate();
onOpenChange(false);
},
onError: (err) => {
setError(err.message);
},
});
// Reset form when dialog opens
useEffect(() => {
if (open) {
setName("");
setDescription("");
setError(null);
}
}, [open]);
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setError(null);
createMutation.mutate({
name: name.trim(),
description: description.trim() || undefined,
});
}
const canSubmit = name.trim().length > 0 && !createMutation.isPending;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Create Initiative</DialogTitle>
<DialogDescription>
Create a new initiative to plan and execute work.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="initiative-name">Name</Label>
<Input
id="initiative-name"
placeholder="e.g. User Authentication"
value={name}
onChange={(e) => setName(e.target.value)}
autoFocus
/>
</div>
<div className="space-y-2">
<Label htmlFor="initiative-description">
Description{" "}
<span className="text-muted-foreground font-normal">
(optional)
</span>
</Label>
<Textarea
id="initiative-description"
placeholder="Brief description of the initiative..."
value={description}
onChange={(e) => setDescription(e.target.value)}
rows={3}
/>
</div>
{error && (
<p className="text-sm text-destructive">{error}</p>
)}
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
>
Cancel
</Button>
<Button type="submit" disabled={!canSubmit}>
{createMutation.isPending ? "Creating..." : "Create"}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}