File size: 6,405 Bytes
f169a35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25cb9ab
f169a35
 
 
 
 
 
25cb9ab
f169a35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import { useState } from "react";
import { apiPost } from "../lib/api";

const CATEGORIES: { value: string; label: string }[] = [
  { value: "bug", label: "Bug" },
  { value: "feature", label: "Feature request" },
  { value: "other", label: "Other" },
];

function FlagIcon() {
  return (
    <svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
      <path d="M5 21V4a1 1 0 0 1 1-1h11.5a.5.5 0 0 1 .4.8l-3 4a.5.5 0 0 0 0 .6l3 4a.5.5 0 0 1-.4.8H6" />
    </svg>
  );
}

export function ReportIssueButton({ className }: { className?: string }) {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button
        onClick={() => setOpen(true)}
        className={className || "flex items-center gap-1.5 text-sm text-white/60 hover:text-white transition-colors"}
        title="Report an issue"
      >
        <FlagIcon />
        Report an issue
      </button>
      {open && <ReportIssueModal onClose={() => setOpen(false)} />}
    </>
  );
}

function ReportIssueModal({ onClose }: { onClose: () => void }) {
  const [category, setCategory] = useState("bug");
  const [subject, setSubject] = useState("");
  const [description, setDescription] = useState("");
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState("");
  const [done, setDone] = useState(false);

  const handleSubmit = async () => {
    if (!subject.trim() || !description.trim()) {
      setError("Please fill in both a subject and a description.");
      return;
    }
    setSubmitting(true);
    setError("");
    try {
      await apiPost("/api/issues", {
        category,
        subject: subject.trim(),
        description: description.trim(),
        page_context: window.location.hash || window.location.pathname,
      });
      setDone(true);
    } catch (e) {
      setError(e instanceof Error ? e.message : "Failed to submit — please try again.");
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <div
      className="fixed inset-0 z-[100] flex items-center justify-center bg-black/40 p-4"
      onClick={onClose}
    >
      <div
        className="w-full max-w-md rounded-2xl bg-[var(--color-surface)] border border-[var(--color-border)] shadow-xl p-6 space-y-4"
        onClick={(e) => e.stopPropagation()}
      >
        {done ? (
          <div className="text-center py-4 space-y-2">
            <div className="mx-auto flex items-center justify-center w-10 h-10 rounded-full bg-emerald-500/10 text-emerald-500">
              <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 12.5 9.5 18 20 6" /></svg>
            </div>
            <p className="text-sm font-medium text-[var(--color-text)]">Thanks — your report was sent.</p>
            <button
              onClick={onClose}
              className="mt-2 text-sm px-4 py-1.5 rounded-lg bg-[var(--color-primary)] text-white hover:bg-[var(--color-primary-light)] transition-colors"
            >
              Close
            </button>
          </div>
        ) : (
          <>
            <div className="flex items-center justify-between">
              <h3 className="text-sm font-bold uppercase tracking-widest text-[var(--color-text)]">Report an Issue</h3>
              <button onClick={onClose} className="text-[var(--color-text-muted)] hover:text-[var(--color-text)] transition-colors" title="Close">
                <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M5 5l14 14M19 5 5 19" /></svg>
              </button>
            </div>

            <div className="space-y-1.5">
              <label className="text-xs font-medium text-[var(--color-text-muted)]">Type</label>
              <div className="flex gap-2">
                {CATEGORIES.map((c) => (
                  <button
                    key={c.value}
                    onClick={() => setCategory(c.value)}
                    className={`text-xs px-3 py-1.5 rounded-full border transition-colors ${
                      category === c.value
                        ? "bg-[var(--color-primary)] text-white border-[var(--color-primary)]"
                        : "text-[var(--color-text-muted)] border-[var(--color-border)] hover:border-[var(--color-primary)]/50"
                    }`}
                  >
                    {c.label}
                  </button>
                ))}
              </div>
            </div>

            <div className="space-y-1.5">
              <label className="text-xs font-medium text-[var(--color-text-muted)]">Subject</label>
              <input
                value={subject}
                onChange={(e) => setSubject(e.target.value)}
                placeholder="Short summary of the issue"
                className="input text-sm"
              />
            </div>

            <div className="space-y-1.5">
              <label className="text-xs font-medium text-[var(--color-text-muted)]">Description</label>
              <textarea
                value={description}
                onChange={(e) => setDescription(e.target.value)}
                placeholder="What happened, what did you expect, and any steps to reproduce it?"
                rows={4}
                className="input text-sm resize-none"
              />
            </div>

            {error && <p className="text-xs text-red-500">{error}</p>}

            <div className="flex justify-end gap-2 pt-1">
              <button
                onClick={onClose}
                className="text-sm px-4 py-1.5 rounded-lg border border-[var(--color-border)] text-[var(--color-text-muted)] hover:text-[var(--color-text)] transition-colors"
              >
                Cancel
              </button>
              <button
                onClick={() => { void handleSubmit(); }}
                disabled={submitting}
                className="text-sm px-4 py-1.5 rounded-lg bg-[var(--color-primary)] text-white hover:bg-[var(--color-primary-light)] disabled:opacity-40 transition-colors"
              >
                {submitting ? "Sending…" : "Send report"}
              </button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}