Spaces:
Sleeping
Sleeping
taboola
add student metrics dashboard, save quiz answer records, save original uploaded files
25cb9ab Download chatkit/frontend/src/components/ReportIssueForm.tsx from taboola-cz/ClassLensPortal: direct link, hf CLI and curl.
- Browser
- Download file 6.41 kB
-
https://huggingface.co/spaces/taboola-cz/ClassLensPortal/resolve/main/chatkit/frontend/src/components/ReportIssueForm.tsx
- Command line
-
hf download hf://spaces/taboola-cz/ClassLensPortal/chatkit/frontend/src/components/ReportIssueForm.tsx
-
curl -L -o ReportIssueForm.tsx https://huggingface.co/spaces/taboola-cz/ClassLensPortal/resolve/main/chatkit/frontend/src/components/ReportIssueForm.tsx
6.41 kB
| 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> | |
| ); | |
| } | |