1import { useState } from 'react'
2import { useNavigate, Link } from 'react-router-dom'
3import { ArrowLeft } from 'lucide-react'
4import { Button } from '@/components/ui/button'
5import { Input } from '@/components/ui/input'
6import { Textarea } from '@/components/ui/textarea'
7import { Markdown } from '@/components/content/Markdown'
8import { useBugCreateMutation } from '@/__generated__/graphql'
9import { useRepo } from '@/lib/repo'
10
11// New issue form (/:repo/issues/new). Title + body with write/preview tabs.
12export function NewBugPage() {
13 const navigate = useNavigate()
14 const repo = useRepo()
15 const [title, setTitle] = useState('')
16 const [message, setMessage] = useState('')
17 const [preview, setPreview] = useState(false)
18
19 const [createBug, { loading, error }] = useBugCreateMutation()
20
21 async function handleSubmit(e: React.FormEvent) {
22 e.preventDefault()
23 if (!title.trim()) return
24 const result = await createBug({
25 variables: { input: { title: title.trim(), message: message.trim() } },
26 })
27 const humanId = result.data?.bugCreate.bug.humanId
28 if (humanId) {
29 navigate(repo ? `/${repo}/issues/${humanId}` : `/issues/${humanId}`)
30 }
31 }
32
33 const issuesHref = repo ? `/${repo}/issues` : '/issues'
34
35 return (
36 <div className="mx-auto max-w-3xl">
37 <Link
38 to={issuesHref}
39 className="mb-6 flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
40 >
41 <ArrowLeft className="size-3.5" />
42 Back to issues
43 </Link>
44
45 <h1 className="mb-6 text-xl font-semibold">New issue</h1>
46
47 <form onSubmit={handleSubmit} className="space-y-4">
48 <div>
49 <label htmlFor="title" className="mb-1.5 block text-sm font-medium">
50 Title
51 </label>
52 <Input
53 id="title"
54 placeholder="Brief description of the issue"
55 value={title}
56 onChange={(e) => setTitle(e.target.value)}
57 required
58 disabled={loading}
59 />
60 </div>
61
62 <div>
63 <div className="mb-1.5 flex items-center justify-between">
64 <label className="text-sm font-medium">Description</label>
65 <div className="flex gap-1 text-sm">
66 <button
67 type="button"
68 onClick={() => setPreview(false)}
69 className={`rounded px-2 py-0.5 transition-colors ${
70 !preview ? 'bg-muted font-medium' : 'text-muted-foreground hover:text-foreground'
71 }`}
72 >
73 Write
74 </button>
75 <button
76 type="button"
77 onClick={() => setPreview(true)}
78 disabled={!message.trim()}
79 className={`rounded px-2 py-0.5 transition-colors disabled:opacity-40 ${
80 preview ? 'bg-muted font-medium' : 'text-muted-foreground hover:text-foreground'
81 }`}
82 >
83 Preview
84 </button>
85 </div>
86 </div>
87
88 {preview ? (
89 <div className="min-h-[200px] rounded-md border border-input px-3 py-2">
90 <Markdown content={message} />
91 </div>
92 ) : (
93 <Textarea
94 placeholder="Describe the issue in detail…"
95 className="min-h-[200px]"
96 value={message}
97 onChange={(e) => setMessage(e.target.value)}
98 disabled={loading}
99 />
100 )}
101 </div>
102
103 {error && (
104 <p className="text-sm text-destructive">Failed to create issue: {error.message}</p>
105 )}
106
107 <div className="flex justify-end gap-2">
108 <Button type="button" variant="ghost" onClick={() => navigate(issuesHref)} disabled={loading}>
109 Cancel
110 </Button>
111 <Button type="submit" disabled={!title.trim() || loading}>
112 {loading ? 'Creating…' : 'Submit new issue'}
113 </Button>
114 </div>
115 </form>
116 </div>
117 )
118}