Download src/components/beta/BetaFeedbackButton.tsx from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 8.49 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/beta/BetaFeedbackButton.tsx
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/components/beta/BetaFeedbackButton.tsx
-
curl -L -o BetaFeedbackButton.tsx https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/beta/BetaFeedbackButton.tsx
8.49 kB
| "use client"; | |
| import { useId, useState } from "react"; | |
| import { MessageSquarePlus, X } from "lucide-react"; | |
| import { cn } from "@/lib/utils"; | |
| import { trackBetaEvent } from "@/lib/beta/betaAnalytics"; | |
| import { | |
| BETA_FEEDBACK_KIND_LABELS, | |
| MAX_FEEDBACK_MESSAGE_LENGTH, | |
| submitBetaFeedback, | |
| type BetaFeedbackKind, | |
| type BetaFeedbackRating, | |
| } from "@/lib/beta/betaFeedback"; | |
| type BetaFeedbackButtonProps = { | |
| route?: string; | |
| subject?: string; | |
| chapterTitle?: string; | |
| missionTitle?: string; | |
| stepLabel?: string; | |
| className?: string; | |
| }; | |
| const KIND_ORDER: BetaFeedbackKind[] = [ | |
| "helped_understand", | |
| "still_confusing", | |
| "wrong_or_doubtful", | |
| "too_long", | |
| "need_more_examples", | |
| "class_confusing", | |
| "bug", | |
| "content_issue", | |
| "ui_issue", | |
| "too_hard", | |
| "too_easy", | |
| "liked", | |
| "other", | |
| ]; | |
| export function BetaFeedbackButton({ | |
| route, | |
| subject, | |
| chapterTitle, | |
| missionTitle, | |
| stepLabel, | |
| className, | |
| }: BetaFeedbackButtonProps) { | |
| const [open, setOpen] = useState(false); | |
| const [kind, setKind] = useState<BetaFeedbackKind>("class_confusing"); | |
| const [rating, setRating] = useState<BetaFeedbackRating | null>(null); | |
| const [message, setMessage] = useState(""); | |
| const [submitted, setSubmitted] = useState(false); | |
| const [error, setError] = useState<string | null>(null); | |
| const titleId = useId(); | |
| const messageId = useId(); | |
| const context = { route, subject, chapterTitle, missionTitle, stepLabel }; | |
| function openPanel() { | |
| setOpen(true); | |
| setSubmitted(false); | |
| setError(null); | |
| trackBetaEvent({ name: "feedback_opened", ...context }); | |
| } | |
| function closePanel() { | |
| setOpen(false); | |
| } | |
| function handleSubmit() { | |
| const result = submitBetaFeedback({ kind, rating: rating ?? undefined, message, ...context }); | |
| if (!result.ok) { | |
| setError(result.error === "empty_message" ? "Please write a short message first." : "Storage unavailable on this device."); | |
| return; | |
| } | |
| setSubmitted(true); | |
| setMessage(""); | |
| setRating(null); | |
| } | |
| return ( | |
| <div className={cn("relative", className)}> | |
| <button | |
| type="button" | |
| onClick={openPanel} | |
| aria-haspopup="dialog" | |
| aria-expanded={open} | |
| aria-label="Open beta feedback" | |
| className="inline-flex min-h-9 items-center gap-2 rounded-lg border border-[#2A2A2A] bg-[#111111] px-3 text-[12px] font-semibold text-white transition hover:bg-[#1A1A1A] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/35" | |
| > | |
| <MessageSquarePlus className="size-3.5" aria-hidden="true" /> | |
| Beta feedback | |
| </button> | |
| {open ? ( | |
| <div | |
| role="dialog" | |
| aria-modal="false" | |
| aria-labelledby={titleId} | |
| data-testid="beta-feedback-panel" | |
| className="absolute right-0 z-40 mt-2 w-[min(92vw,340px)] rounded-xl border border-[#2A2A2A] bg-[#0B0B0B] p-4 shadow-[0_24px_80px_rgba(0,0,0,0.5)]" | |
| > | |
| <div className="flex items-start justify-between gap-2"> | |
| <div> | |
| <p id={titleId} className="text-[13px] font-bold text-white"> | |
| Beta feedback | |
| </p> | |
| <p className="mt-1 text-[11px] leading-4 text-[#8B8B8B]">Tell us what confused you or what broke.</p> | |
| </div> | |
| <button | |
| type="button" | |
| onClick={closePanel} | |
| aria-label="Close beta feedback" | |
| className="rounded-lg border border-[#2A2A2A] bg-[#111111] p-1.5 text-[#A1A1AA] transition hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/35" | |
| > | |
| <X className="size-3.5" aria-hidden="true" /> | |
| </button> | |
| </div> | |
| {submitted ? ( | |
| <div className="mt-4" data-testid="beta-feedback-confirmation"> | |
| <p className="text-[13px] font-semibold text-white">Thanks! Feedback saved.</p> | |
| <p className="mt-1 text-[11px] leading-4 text-[#8B8B8B]">Stored on this device for beta testing.</p> | |
| <button | |
| type="button" | |
| onClick={closePanel} | |
| className="mt-3 inline-flex min-h-9 w-full items-center justify-center rounded-lg bg-white px-3 text-[12px] font-bold text-black transition hover:bg-white/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/35" | |
| > | |
| Close feedback | |
| </button> | |
| </div> | |
| ) : ( | |
| <div className="mt-3 space-y-3"> | |
| <div> | |
| <label htmlFor={`${titleId}-kind`} className="text-[10px] font-semibold uppercase tracking-wider text-[#6B7280]"> | |
| What is this about? | |
| </label> | |
| <select | |
| id={`${titleId}-kind`} | |
| aria-label="What is this about?" | |
| value={kind} | |
| onChange={(event) => setKind(event.target.value as BetaFeedbackKind)} | |
| className="mt-1.5 w-full rounded-lg border border-[#2A2A2A] bg-[#050505] p-2 text-[12px] text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/35" | |
| > | |
| {KIND_ORDER.map((value) => ( | |
| <option key={value} value={value}> | |
| {BETA_FEEDBACK_KIND_LABELS[value]} | |
| </option> | |
| ))} | |
| </select> | |
| </div> | |
| <div> | |
| <fieldset className="mt-1.5 flex gap-1.5" aria-label="Rating (optional)"> | |
| <legend className="sr-only">Rating (optional)</legend> | |
| {([1, 2, 3, 4, 5] as BetaFeedbackRating[]).map((value) => ( | |
| <button | |
| key={value} | |
| type="button" | |
| aria-label={`Rate ${value} out of 5`} | |
| aria-pressed={rating === value} | |
| onClick={() => setRating((current) => (current === value ? null : value))} | |
| className={cn( | |
| "size-8 rounded-lg border text-[12px] font-bold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/35", | |
| rating === value | |
| ? "border-white bg-white text-black" | |
| : "border-[#2A2A2A] bg-[#050505] text-white hover:bg-[#151515]", | |
| )} | |
| > | |
| {value} | |
| </button> | |
| ))} | |
| </fieldset> | |
| </div> | |
| <div> | |
| <label htmlFor={messageId} className="text-[10px] font-semibold uppercase tracking-wider text-[#6B7280]"> | |
| Message | |
| </label> | |
| <textarea | |
| id={messageId} | |
| aria-label="Message" | |
| value={message} | |
| maxLength={MAX_FEEDBACK_MESSAGE_LENGTH} | |
| onChange={(event) => { | |
| setMessage(event.target.value); | |
| if (error) setError(null); | |
| }} | |
| rows={4} | |
| placeholder="What confused you or what broke?" | |
| className="mt-1.5 w-full resize-y rounded-lg border border-[#2A2A2A] bg-[#050505] p-2.5 text-[12px] leading-5 text-white placeholder:text-[#5F6368] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/35" | |
| /> | |
| <div className="mt-1 flex items-center justify-between"> | |
| <p className="text-[10px] text-[#5F6368]">{message.length}/{MAX_FEEDBACK_MESSAGE_LENGTH}</p> | |
| {error ? <p className="text-[10px] font-semibold text-white">{error}</p> : null} | |
| </div> | |
| </div> | |
| <p className="text-[10px] leading-4 text-[#6B7280]"> | |
| Beta feedback is saved on this device. Do not include private personal details. | |
| </p> | |
| <button | |
| type="button" | |
| onClick={handleSubmit} | |
| className="inline-flex min-h-9 w-full items-center justify-center rounded-lg bg-white px-3 text-[12px] font-bold text-black transition hover:bg-white/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/35" | |
| > | |
| Submit feedback | |
| </button> | |
| </div> | |
| )} | |
| </div> | |
| ) : null} | |
| </div> | |
| ); | |
| } | |