Files
pieced-portal/src/components/support/ticket-create-form.tsx
admin 8273d08f15
All checks were successful
Build and Push / build (push) Successful in 1m30s
Support org
2026-05-02 10:50:06 +02:00

131 lines
4.3 KiB
TypeScript

"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { Card } from "@/components/ui/card";
import type { SupportTicketCategory } from "@/types";
const CATEGORIES: SupportTicketCategory[] = [
"bug",
"feature_request",
"question",
"billing",
"other",
];
export function TicketCreateForm() {
const t = useTranslations("support");
const tCommon = useTranslations("common");
const router = useRouter();
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [category, setCategory] = useState<SupportTicketCategory>("question");
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState("");
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setSubmitting(true);
setError("");
try {
const res = await fetch("/api/support/tickets", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title, description, category }),
});
if (!res.ok) {
const data = await res.json().catch(() => ({}));
throw new Error(data.error || t("createFailed"));
}
const data = await res.json();
// Redirect to the new ticket's detail page so the customer can
// see the confirmation state and immediately add follow-ups if
// they wish.
router.push(`/support/${data.ticket.id}`);
router.refresh();
} catch (e: any) {
setError(e.message);
setSubmitting(false);
}
};
return (
<Card>
<form onSubmit={onSubmit} className="space-y-4">
<div>
<label className="block text-xs uppercase tracking-wider text-text-muted mb-1">
{t("fieldCategory")} <span className="text-red-400">*</span>
</label>
<select
required
value={category}
onChange={(e) =>
setCategory(e.target.value as SupportTicketCategory)
}
className="w-full px-3 py-2 rounded-lg border border-border bg-surface-2 text-text-primary text-sm focus:outline-none focus:border-text-secondary"
>
{CATEGORIES.map((c) => (
<option key={c} value={c}>
{t(`category_${c}`)}
</option>
))}
</select>
</div>
<div>
<label className="block text-xs uppercase tracking-wider text-text-muted mb-1">
{t("fieldTitle")} <span className="text-red-400">*</span>
</label>
<input
type="text"
required
minLength={3}
maxLength={200}
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder={t("titlePlaceholder")}
className="w-full px-3 py-2 rounded-lg border border-border bg-surface-2 text-text-primary text-sm focus:outline-none focus:border-text-secondary"
/>
</div>
<div>
<label className="block text-xs uppercase tracking-wider text-text-muted mb-1">
{t("fieldDescription")} <span className="text-red-400">*</span>
</label>
<textarea
required
minLength={10}
maxLength={10_000}
rows={8}
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder={t("descriptionPlaceholder")}
className="w-full px-3 py-2 rounded-lg border border-border bg-surface-2 text-text-primary text-sm focus:outline-none focus:border-text-secondary"
/>
<p className="text-xs text-text-muted mt-1">
{t("descriptionHelp")}
</p>
</div>
{error && (
<div className="text-xs text-red-400 bg-red-400/10 border border-red-400/20 rounded-lg px-3 py-2">
{error}
</div>
)}
<div className="flex justify-end">
<button
type="submit"
disabled={submitting}
className="text-sm font-medium px-4 py-2 rounded-lg bg-accent text-white hover:bg-accent/90 transition-colors disabled:opacity-50"
>
{submitting ? tCommon("loading") : t("submitTicket")}
</button>
</div>
</form>
</Card>
);
}