NewBugPage.tsx

  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}