1+ import { fetch as tauriFetch } from "@tauri-apps/plugin-http" ;
12import { arch , version as osVersion , platform } from "@tauri-apps/plugin-os" ;
23import { Bug , Lightbulb , X } from "lucide-react" ;
34import { useCallback , useEffect , useState } from "react" ;
@@ -8,7 +9,6 @@ import { commands as miscCommands } from "@hypr/plugin-misc";
89import { commands as openerCommands } from "@hypr/plugin-opener2" ;
910import { commands as tracingCommands } from "@hypr/plugin-tracing" ;
1011import { Button } from "@hypr/ui/components/ui/button" ;
11- import { Checkbox } from "@hypr/ui/components/ui/checkbox" ;
1212import { cn } from "@hypr/utils" ;
1313
1414import { env } from "../../env" ;
@@ -29,22 +29,14 @@ export const useFeedbackModal = create<FeedbackModalStore>((set) => ({
2929 close : ( ) => set ( { isOpen : false } ) ,
3030} ) ) ;
3131
32- async function openLogsDir ( ) : Promise < boolean > {
33- const result = await tracingCommands . logsDir ( ) ;
34- if ( result . status === "ok" ) {
35- const revealResult = await openerCommands . revealItemInDir ( result . data ) ;
36- return revealResult . status === "ok" ;
37- }
38- return false ;
39- }
40-
4132export function FeedbackModal ( ) {
4233 const { isOpen, initialType, close } = useFeedbackModal ( ) ;
4334 const [ type , setType ] = useState < FeedbackType > ( initialType ) ;
4435 const [ description , setDescription ] = useState ( "" ) ;
36+ const [ attachLogs , setAttachLogs ] = useState ( true ) ;
4537 const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
46- const [ gitHash , setGitHash ] = useState < string > ( "" ) ;
47- const [ attachLogs , setAttachLogs ] = useState ( false ) ;
38+ const [ submitStatus , setSubmitStatus ] = useState < string > ( "" ) ;
39+ const [ errorMessage , setErrorMessage ] = useState < string > ( "" ) ;
4840
4941 useEffect ( ( ) => {
5042 const handleEscape = ( e : KeyboardEvent ) => {
@@ -65,98 +57,85 @@ export function FeedbackModal() {
6557 useEffect ( ( ) => {
6658 if ( isOpen ) {
6759 setType ( initialType ) ;
68- miscCommands . getGitHash ( ) . then ( ( result ) => {
69- setGitHash ( result . status === "ok" ? result . data : "unknown" ) ;
70- } ) ;
7160 } else {
7261 setDescription ( "" ) ;
73- setGitHash ( "" ) ;
74- setAttachLogs ( false ) ;
62+ setAttachLogs ( true ) ;
63+ setSubmitStatus ( "" ) ;
64+ setErrorMessage ( "" ) ;
7565 }
7666 } , [ isOpen , initialType ] ) ;
7767
7868 const handleSubmit = useCallback ( async ( ) => {
79- if ( ! description . trim ( ) ) {
69+ const trimmed = description . trim ( ) ;
70+ if ( ! trimmed ) {
8071 return ;
8172 }
8273
74+ if ( trimmed . length < 10 ) {
75+ setErrorMessage ( "Description must be at least 10 characters" ) ;
76+ return ;
77+ }
78+
79+ setErrorMessage ( "" ) ;
8380 setIsSubmitting ( true ) ;
81+ setSubmitStatus ( "Submitting..." ) ;
8482
8583 try {
8684 const gitHashResult = await miscCommands . getGitHash ( ) ;
8785 const gitHash =
8886 gitHashResult . status === "ok" ? gitHashResult . data : "unknown" ;
8987
90- const deviceInfo = [
91- `**Platform:** ${ platform ( ) } ` ,
92- `**Architecture:** ${ arch ( ) } ` ,
93- `**OS Version:** ${ osVersion ( ) } ` ,
94- `**App Version:** ${ env . VITE_APP_VERSION ?? "unknown" } ` ,
95- `**Git Hash:** ${ gitHash } ` ,
96- ] . join ( "\n" ) ;
97-
98- const trimmedDescription = description . trim ( ) ;
99- const firstLine = trimmedDescription . split ( "\n" ) [ 0 ] . slice ( 0 , 100 ) . trim ( ) ;
100- const title =
101- firstLine || ( type === "bug" ? "Bug Report" : "Feature Request" ) ;
102-
103- let logSection = "" ;
104- if ( attachLogs ) {
105- const logsOpened = await openLogsDir ( ) ;
106- if ( logsOpened ) {
107- logSection = `
108-
109- ## Application Logs
110- Logs will be opened in a separate window. Please attach the log file to this issue.
111- ` ;
88+ let logs : string | undefined ;
89+ if ( type === "bug" && attachLogs ) {
90+ setSubmitStatus ( "Collecting logs..." ) ;
91+ const logsResult = await tracingCommands . logContent ( ) ;
92+ if ( logsResult . status === "ok" && logsResult . data ) {
93+ logs = logsResult . data . slice ( - 10000 ) ;
11294 }
11395 }
11496
115- if ( type === "bug" ) {
116- const body = `## Description
117- ${ trimmedDescription }
118-
119- ## Device Information
120- ${ deviceInfo }
121- ${ logSection }
122- ---
123- *This issue was submitted from the Hyprnote desktop app.*
124- ` ;
97+ setSubmitStatus ( "Submitting..." ) ;
98+
99+ const response = await tauriFetch ( `${ env . VITE_API_URL } /feedback/submit` , {
100+ method : "POST" ,
101+ headers : { "Content-Type" : "application/json" } ,
102+ body : JSON . stringify ( {
103+ type,
104+ description : trimmed ,
105+ logs,
106+ deviceInfo : {
107+ platform : platform ( ) ,
108+ arch : arch ( ) ,
109+ osVersion : osVersion ( ) ,
110+ appVersion : env . VITE_APP_VERSION ?? "unknown" ,
111+ gitHash,
112+ } ,
113+ } ) ,
114+ } ) ;
125115
126- const url = new URL ( "https://github.com/fastrepl/hyprnote/issues/new" ) ;
127- url . searchParams . set ( "title" , title ) ;
128- url . searchParams . set ( "body" , body ) ;
129- url . searchParams . set ( "labels" , "bug,user-reported" ) ;
116+ const data = ( await response . json ( ) ) as {
117+ success : boolean ;
118+ issueUrl ?: string ;
119+ error ?: string ;
120+ } ;
130121
131- await openerCommands . openUrl ( url . toString ( ) , null ) ;
122+ if ( data . success && data . issueUrl ) {
123+ await openerCommands . openUrl ( data . issueUrl , null ) ;
124+ close ( ) ;
132125 } else {
133- const body = `## Feature Request
134- ${ trimmedDescription }
135-
136- ## Submitted From
137- ${ deviceInfo }
138- ${ logSection }
139- ---
140- *This feature request was submitted from the Hyprnote desktop app.*
141- ` ;
142-
143- const url = new URL (
144- "https://github.com/fastrepl/hyprnote/discussions/new" ,
145- ) ;
146- url . searchParams . set ( "category" , "ideas" ) ;
147- url . searchParams . set ( "title" , title ) ;
148- url . searchParams . set ( "body" , body ) ;
149-
150- await openerCommands . openUrl ( url . toString ( ) , null ) ;
126+ setErrorMessage ( data . error ?? "Failed to submit feedback" ) ;
151127 }
152-
153- close ( ) ;
154128 } catch ( error ) {
155- console . error ( "Failed to submit feedback:" , error ) ;
129+ console . error (
130+ "Failed to submit feedback:" ,
131+ error instanceof Error ? error . message : String ( error ) ,
132+ ) ;
133+ setErrorMessage ( "Failed to submit feedback. Please try again." ) ;
156134 } finally {
157135 setIsSubmitting ( false ) ;
136+ setSubmitStatus ( "" ) ;
158137 }
159- } , [ description , type , close , attachLogs ] ) ;
138+ } , [ description , type , attachLogs , close ] ) ;
160139
161140 if ( ! isOpen ) {
162141 return null ;
@@ -234,7 +213,10 @@ ${logSection}
234213 < textarea
235214 id = "feedback-description"
236215 value = { description }
237- onChange = { ( e ) => setDescription ( e . target . value ) }
216+ onChange = { ( e ) => {
217+ setDescription ( e . target . value ) ;
218+ if ( errorMessage ) setErrorMessage ( "" ) ;
219+ } }
238220 placeholder = {
239221 isBug
240222 ? "What happened? What did you expect to happen? Steps to reproduce..."
@@ -243,32 +225,30 @@ ${logSection}
243225 rows = { 6 }
244226 className = { cn ( [
245227 "w-full px-2.5 py-1.5 rounded-md" ,
246- "border border-neutral-200" ,
228+ "border" ,
229+ errorMessage ? "border-red-500" : "border-neutral-200" ,
247230 "text-sm resize-none" ,
248231 "focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-1" ,
249232 ] ) }
250233 maxLength = { 5000 }
251234 />
235+ { errorMessage && (
236+ < p className = "text-xs text-red-500 mt-1" > { errorMessage } </ p >
237+ ) }
252238 </ div >
253239
254- < div className = "flex items-center gap-2" >
255- < Checkbox
256- id = "attach-logs"
257- checked = { attachLogs }
258- onCheckedChange = { ( checked ) => setAttachLogs ( checked === true ) }
259- />
260- < label
261- htmlFor = "attach-logs"
262- className = "text-sm text-neutral-600 cursor-pointer"
263- >
264- Open log directory (for manual attachment)
240+ { isBug && (
241+ < label className = "flex items-center gap-2 cursor-pointer" >
242+ < input
243+ type = "checkbox"
244+ checked = { attachLogs }
245+ onChange = { ( e ) => setAttachLogs ( e . target . checked ) }
246+ className = "rounded border-neutral-300"
247+ />
248+ < span className = "text-sm text-neutral-600" >
249+ Attach app logs to help diagnose the issue
250+ </ span >
265251 </ label >
266- </ div >
267-
268- { gitHash && (
269- < div className = "mt-4 text-[10px] text-neutral-100 font-mono" >
270- { gitHash }
271- </ div >
272252 ) }
273253 </ div >
274254
@@ -279,7 +259,7 @@ ${logSection}
279259 className = "h-8 text-sm"
280260 >
281261 { isSubmitting
282- ? "Opening..."
262+ ? submitStatus || "Opening..."
283263 : isBug
284264 ? "Report Bug"
285265 : "Suggest Feature" }
0 commit comments