126 lines
4.6 KiB
TypeScript
126 lines
4.6 KiB
TypeScript
"use client";
|
||
|
||
import { useState, type FormEvent } from "react";
|
||
import { trackEvent } from "@/lib/analytics-events";
|
||
import type { Dictionary } from "@/lib/i18n";
|
||
|
||
type FeedbackType = "bug" | "suggestion" | "general";
|
||
|
||
type FeedbackModalProps = {
|
||
dateKey: string;
|
||
onClose: () => void;
|
||
t: Dictionary;
|
||
};
|
||
|
||
export default function FeedbackModal({ dateKey, onClose, t }: FeedbackModalProps) {
|
||
const [type, setType] = useState<FeedbackType>("general");
|
||
const [message, setMessage] = useState("");
|
||
const [isSending, setIsSending] = useState(false);
|
||
const [status, setStatus] = useState<"idle" | "success" | "error">("idle");
|
||
|
||
async function submit(event: FormEvent<HTMLFormElement>) {
|
||
event.preventDefault();
|
||
if (!message.trim() || isSending) return;
|
||
|
||
setIsSending(true);
|
||
setStatus("idle");
|
||
|
||
try {
|
||
const response = await fetch("/api/feedback", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({
|
||
dateKey,
|
||
message: message.trim(),
|
||
type,
|
||
page: window.location.pathname,
|
||
locale: window.navigator.language,
|
||
source: "hidden11",
|
||
}),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error("feedback request failed");
|
||
}
|
||
|
||
setStatus("success");
|
||
setMessage("");
|
||
trackEvent("feedback_sent", { type });
|
||
} catch {
|
||
setStatus("error");
|
||
} finally {
|
||
setIsSending(false);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="fixed inset-0 z-50 flex items-end bg-black/70 p-3 backdrop-blur-sm sm:items-center sm:justify-center">
|
||
<div className="w-full rounded-lg border border-white/10 bg-pitch-950 shadow-2xl sm:max-w-lg" role="dialog" aria-modal="true" aria-labelledby="feedback-title">
|
||
<div className="flex items-center justify-between gap-3 border-b border-white/10 p-4">
|
||
<div>
|
||
<h2 id="feedback-title" className="text-lg font-black text-white">
|
||
{t.feedback}
|
||
</h2>
|
||
<p className="mt-0.5 text-sm text-white/55">Tell us what to improve.</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={onClose}
|
||
className="h-9 w-9 rounded-full border border-white/15 text-lg font-black text-white transition hover:bg-white/10"
|
||
aria-label={t.closeModal}
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
|
||
<form className="space-y-4 p-4" onSubmit={submit}>
|
||
<label className="block">
|
||
<span className="mb-2 block text-xs font-black uppercase text-white/55">{t.feedbackType}</span>
|
||
<select
|
||
value={type}
|
||
onChange={(event) => setType(event.target.value as FeedbackType)}
|
||
className="h-11 w-full rounded-lg border border-white/10 bg-white/[0.06] px-3 text-sm text-white outline-none"
|
||
>
|
||
<option value="bug">{t.feedbackBug}</option>
|
||
<option value="suggestion">{t.feedbackSuggestion}</option>
|
||
<option value="general">{t.feedbackGeneral}</option>
|
||
</select>
|
||
</label>
|
||
|
||
<label className="block">
|
||
<span className="mb-2 block text-xs font-black uppercase text-white/55">{t.feedbackMessage}</span>
|
||
<textarea
|
||
required
|
||
minLength={8}
|
||
value={message}
|
||
onChange={(event) => setMessage(event.target.value)}
|
||
placeholder={t.feedbackPlaceholder}
|
||
className="min-h-36 w-full rounded-lg border border-white/10 bg-white/[0.06] px-3 py-2 text-sm text-white outline-none placeholder:text-white/35"
|
||
/>
|
||
</label>
|
||
|
||
{status === "success" ? <p className="text-sm font-semibold text-emerald-200">{t.feedbackSuccess}</p> : null}
|
||
{status === "error" ? <p className="text-sm font-semibold text-red-200">{t.feedbackError}</p> : null}
|
||
|
||
<div className="flex items-center justify-end gap-2">
|
||
<button
|
||
type="button"
|
||
onClick={onClose}
|
||
className="h-10 rounded-full border border-white/10 px-4 text-sm font-black text-white transition hover:bg-white/10"
|
||
>
|
||
{t.close}
|
||
</button>
|
||
<button
|
||
type="submit"
|
||
disabled={isSending}
|
||
className="h-10 rounded-full bg-emerald-400 px-5 text-sm font-black text-pitch-950 transition hover:bg-emerald-300 disabled:cursor-not-allowed disabled:opacity-60"
|
||
>
|
||
{isSending ? "..." : t.feedbackSend}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|