Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Bug report
description: File a bug report.
title: "bug: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to report a bug! Please provide the details below.

- type: input
id: ver
attributes:
label: App Version
description: The version of the app you are using.
validations:
required: true

- type: input
id: os
attributes:
label: Operating system
description: Which OS are you using?
validations:
required: true

- type: textarea
id: description
attributes:
label: Describe the bug
description: What happened?
placeholder: Tell us what you saw.
validations:
required: true

- type: textarea
id: reproduce
attributes:
label: How to reproduce
description: List the minimal steps to trigger the issue.
placeholder: |
1. …
2. …
3. …
validations:
required: true

- type: textarea
id: expected
attributes:
label: Expected behavior
description: What did you expect to happen instead?
placeholder: Tell us what you expected.

- type: textarea
id: logs
attributes:
label: Relevant log output
description: Click the clipboard button in the app to copy your logs.
placeholder: "Paste your logs here."
render: shell


- type: textarea
id: attachments
attributes:
label: Attachments
description: Paste any relevant screenshots here
placeholder: Drag & drop or paste images.
154 changes: 74 additions & 80 deletions tauri/src/windows/main-window/report.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { invoke } from "@tauri-apps/api/core";
import { open as openInBrowser } from "@tauri-apps/plugin-shell";
import { platform, version } from "@tauri-apps/plugin-os";
import { appVersion } from "@/windows/window-utils.ts";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { FileInput } from "@/components/ui/file-input";
import * as Sentry from "@sentry/react";
import { readTextFile } from "@tauri-apps/plugin-fs";
import useStore from "@/store/store";
import { Clipboard } from "lucide-react";

const OWNER = "gethopp";
const REPO = "hopp";
const TEMPLATE = "bug_report.yml";

const getLogs = async () => {
const logs = await invoke<string | null>("get_logs");
return logs;
const getLogs = async (): Promise<string> => {
try {
const logPath = await invoke<string | null>("get_logs");
if (!logPath) {
return "Log file path could not be found";
}
const logContent = await readTextFile(logPath);
if (!logContent.trim()) {
return "The log file is empty";
}
return logContent;
} catch (error) {
return `An error occurred while trying to read the log file: ${error}`;
}
};

const deactivateHiding = async (value: boolean) => {
await invoke("set_deactivate_hiding", { deactivate: value });
};

export function Report() {
const [description, setDescription] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const user = useStore((state) => state.user);
const [isCopying, setIsCopying] = useState(false);

useEffect(() => {
deactivateHiding(true);
Expand All @@ -30,89 +44,69 @@ export function Report() {
};
}, []);

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

if (!description.trim()) {
toast.error("Please provide a description of the issue");
return;
const handleCopyLogs = async () => {
setIsCopying(true);
try {
const logs = await getLogs();
await writeText(logs);
toast.success("App logs copied to clipboard!");
} catch (error) {
toast.error("Could not copy logs.");
} finally {
setIsCopying(false);
}
};

const handleOpenTemplate = async () => {
setIsSubmitting(true);
try {
let blob = await selectedFile?.arrayBuffer();

if (user?.email) {
Sentry.getCurrentScope().setUser({
email: user.email.trim(),
});
}

if (blob !== undefined) {
Sentry.getCurrentScope().addAttachment({
data: new Uint8Array(blob),
filename: selectedFile ? selectedFile.name : "screenshot.png",
});
}

Sentry.getCurrentScope().addAttachment({
data: description,
filename: "description.txt",
const osPlatform = await platform();
const osVersion = await version();

const base = `https://github.com/${OWNER}/${REPO}/issues/new`;
const params = new URLSearchParams({
template: TEMPLATE,
os: `${osPlatform} ${osVersion}`,
ver: `${appVersion}`,
});
const url = `${base}?${params.toString()}`;

const logs = await getLogs();

if (logs !== null) {
const logs_content = await readTextFile(logs);
Sentry.getCurrentScope().addAttachment({
data: logs_content,
filename: "logs.txt",
});
try {
await openInBrowser(url);
} catch {
window.open(url, "_blank", "noopener");
}

const reportId = Math.random().toString(36).substring(7);
Sentry.captureMessage(`User reported an issue (${reportId})`);

Sentry.getCurrentScope().clearAttachments();
} catch (error) {
toast.error("Failed to submit report");
console.error(error);
return;
console.error("Failed to open bug report form:", error);
toast.error("Could not open bug report form.");
} finally {
setIsSubmitting(false);
}
setIsSubmitting(false);
toast.success("Report submitted successfully", {
duration: 5000,
});
};

return (
<div className="flex flex-col p-6 max-w-2xl mx-auto">
<h1 className="text-2xl font-semibold mb-4">Report an Issue</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<label htmlFor="description" className="block text-sm font-medium">
Description
</label>
<Textarea
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Please describe the issue you're experiencing..."
className="min-h-[150px]"
required
/>
<FileInput
acceptedFileTypes="PNG, JPEG, JPG or MP4"
maxSize={15}
onFileSelect={(file) => {
setSelectedFile(file);
}}
/>
</div>
<Button type="submit" className="w-full" disabled={isSubmitting}>
{isSubmitting ? "Submitting..." : "Submit Report"}
<h1 className="text-2xl font-semibold mb-2">Report an Issue</h1>
<p className="text-sm text-muted-foreground mb-6">
This opens a GitHub bug report form in your browser. Use the clipboard icon to copy logs.
</p>

<div className="flex w-full items-center space-x-2">
<Button onClick={handleOpenTemplate} className="w-full" disabled={isSubmitting}>
{isSubmitting ? "Opening GitHub…" : "Open Bug Report Form"}
</Button>
<Button onClick={handleCopyLogs} variant="outline" size="icon" disabled={isCopying}>
<Clipboard className="h-4 w-4" />
</Button>
</form>
</div>

<div className="mt-4 text-xs text-muted-foreground">
Learn more in the official project{" "}
<a href="https://docs.gethopp.app/" target="_blank" rel="noreferrer" className="underline hover-no-underline">
documentation
</a>
.
</div>
</div>
);
}
Expand Down