Something went wrong. Try again.
Starship Simulator Controls thoriumsim.com
Something went wrong. Try again.
7.3 kB · 236 lines
TSX
at develop
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237import { IssueTrackerContext } from "@thorium/components/useIssueTracker";import { toast } from "@thorium/context/ToastContext";import Button from "@thorium/ui/Button";import Input from "@thorium/ui/Input";import MarkdownInput from "@thorium/ui/MarkdownInput";import Modal from "@thorium/ui/Modal";import randomWords from "@thorium/utils/random-words";import { type Dispatch, type ReactNode, type SetStateAction, useEffect, useState } from "react";
import packageJson from "../../package.json" with { type: "json" };import { useThoriumAccount } from "../context/ThoriumAccountContext";import LoginButton from "./LoginButton";import { Icon } from "./ui/Icon";
const availableLabels = ["Feature", "Bug", "Design", "Documentation", "Question", "Compliment"];
export const IssueTrackerProvider = ({ children }: { children: ReactNode }) => { const [open, setOpen] = useState(false);
return ( <IssueTrackerContext.Provider value={{ open, setOpen }}> {children} <IssueTracker open={open} setOpen={setOpen} /> </IssueTrackerContext.Provider> );};
function IssueTracker({ open, setOpen,}: { open: boolean; setOpen: Dispatch<SetStateAction<boolean>>;}) { const { account, refresh } = useThoriumAccount(); const [body, setBody] = useState(""); const [invalid, setInvalid] = useState<string[]>([]);
useEffect( function revalidateOnVisibilityChange() { if (!open) return; function onVisibilityChange() { refresh(); } window.addEventListener("visibilitychange", onVisibilityChange); return () => window.removeEventListener("visibilitychange", onVisibilityChange); }, [refresh, open], ); useEffect( function revalidateOnFocus() { if (!open) return; function onFocus() { refresh(); } window.addEventListener("focus", onFocus); return () => window.removeEventListener("focus", onFocus); }, [refresh, open], ); return ( <Modal isOpen={open} setIsOpen={(open) => setOpen(open)} title="Submit an Issue"> {!account ? ( <div className="my-8 flex flex-col items-center justify-center gap-8 text-center"> <h2 className="text-3xl font-bold">Sign in to Submit Issues</h2> <p className="w-64"> To better track issues and collect feedback, you must be logged in to a Thorium Nova account. </p> <div> <LoginButton buttonClassName="btn-notice" /> </div> </div> ) : !account.accounts?.includes("github") ? ( <div className="my-8 flex flex-col items-center justify-center gap-8 text-center"> <h2 className="text-3xl font-bold">Connect to Github</h2> <p className="w-64"> To submit issues, you must connect your Thorium Nova account to Github. </p> <div> <a href="https://thoriumsim.com/profile" target="thoriumsim.com" className="btn btn-notice" > Connect Account <Icon name="external-link" className="ml-4" /> </a> </div> </div> ) : ( <form className="my-4 flex w-96 flex-col gap-4" onSubmit={async (event) => { event.preventDefault(); const title = event.currentTarget.issueTitle.value; const label = event.currentTarget.label.value; let isInvalid = false; if (!title) { isInvalid = true; setInvalid((invalid) => invalid.concat("title")); } if (!label || label === "Select One") { isInvalid = true; setInvalid((invalid) => invalid.concat("type")); } if (isInvalid) { return; } const requestBody = { repo: "thorium-nova", title, body: `${body} ### Version: ${packageJson.version}`, labels: [label], }; setOpen(false); try { const response = await fetch(`${process.env.THORIUMSIM_URL}/api/issues`, { method: "POST", body: JSON.stringify(requestBody), headers: { "Content-Type": "application/json", Authorization: `Bearer ${account.access_token}`, }, }); const result = (await response.json()) as any; if (!response.ok || result.error) { toast({ title: "Error Submitting Issue", body: result.error || "An unknown error occurred", color: "error", }); return; }
toast({ title: "Issue Submitted!", color: "success" }); setBody(""); } catch (err) { if (err instanceof Error) { toast({ title: "Error Submitting Issue", body: err.message || "An unknown error occurred", color: "error", }); } if (typeof err === "string") { toast({ title: "Error Submitting Issue", body: err || "An unknown error occurred", color: "error", }); } } }} > <Input label="Title" name="issueTitle" isInvalid={invalid.includes("title")} invalidMessage="Title is required." onFocus={() => setInvalid(invalid.filter((i) => i !== "title"))} /> <label className="flex flex-col" htmlFor="body"> Body <MarkdownInput value={body} setValue={(newBody) => { setBody(newBody); }} name="body" saveImage={async function* saveImage(data: ArrayBuffer) { const uploadDataRequest = await fetch( `${process.env.THORIUMSIM_URL}/api/uploadData`, { headers: { Authorization: `Bearer ${account.access_token}`, }, }, ); const uploadData = (await uploadDataRequest.json()) as any; if (!uploadDataRequest.ok) { toast({ title: "Error Uploading File", body: uploadData.error, color: "error", }); throw new Error(uploadData.error); } const nameParts = randomWords(3); const name = `${ Array.isArray(nameParts) ? nameParts.join("-") : nameParts }-${Date.now()}`; const image = new File([new Blob([data])], `${name}.png`, { type: "image/png", }); const result = (await fetch(uploadData.uploadUrl, { method: "POST", body: image, headers: { Authorization: uploadData.authorizationToken, "Content-Type": "b2/x-auto", "X-Bz-File-Name": `issue_uploads/${image.name.replace(/\s/gm, "-")}`, "X-Bz-Content-Sha1": "do_not_verify", }, }).then((res) => res.json())) as any; const { fileName } = result; const url = `https://files.thoriumsim.com/file/thorium-public/${fileName}`; yield url; // returns true meaning that the save was successful return true; }} /> </label> <Input as="select" label="Type" className="form-select select select-sm block w-full max-w-xs" isInvalid={invalid.includes("type")} invalidMessage="Type is required." onChange={(e) => e.target.value !== "Select One" && setInvalid(invalid.filter((i) => i !== "type")) } name="label" > <option>Select One</option> {availableLabels.map((label) => ( <option key={label}>{label}</option> ))} </Input> <Button className="btn-success self-end">Submit</Button> </form> )} </Modal> );}