Backend (prs.go):
Update — PATCH /{prID} edits title and/or body, validates title non-empty, returns prWithReviewers
Reopen — POST /{prID}/reopen transitions closed → open, fires webhook
Close now returns prWithReviewers and fires a webhook
Merge already existed; no changes needed
Frontend — PRDetailPage.tsx full rewrite:
Inline title editing — pencil icon (visible to author/admin when open), Enter to save, Esc to cancel
Inline body editing — same pattern in the description panel
Merge sidebar — radio buttons for allowed strategies (fetched from repo's merge strategy settings), "Merge pull request" button in Bitbucket purple, "Close without merging" below it
Status banner — merged (purple) or closed (grey) with the date, shown below the description
File list — scrollable +N −N table above the diff viewer showing all changed files with addition/deletion counts
Reopen button — appears in the sidebar when the PR is closed
Reviewers panel — lists assigned reviewers with avatars/initials
Details panel — from/into branches, opened date, last updated
Quick links — back to all PRs, open new PR
PRsPage.tsx — now shows real data:
Two tabs: "My pull requests" and "Awaiting my review" (with live counts from dashboard)
Per-repo quick links at the bottom showing open PR count badges
This commit is contained in:
@@ -3,6 +3,12 @@ import { z } from 'zod'
|
||||
import { api } from '../client'
|
||||
import type { PullRequest } from '../../types/api'
|
||||
|
||||
const prReviewerSchema = z.object({
|
||||
userId: z.number(),
|
||||
username: z.string(),
|
||||
avatarUrl: z.string(),
|
||||
})
|
||||
|
||||
const prSchema = z.object({
|
||||
id: z.number(),
|
||||
repoId: z.number(),
|
||||
@@ -14,6 +20,7 @@ const prSchema = z.object({
|
||||
status: z.enum(['open', 'merged', 'closed']),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
reviewers: z.array(prReviewerSchema).default([]),
|
||||
})
|
||||
|
||||
const prsSchema = z.array(prSchema)
|
||||
@@ -38,13 +45,62 @@ export function usePR(owner: string, repo: string, prId: number) {
|
||||
})
|
||||
}
|
||||
|
||||
export function useMergePR(owner: string, repo: string) {
|
||||
export function useUpdatePR(owner: string, repo: string, prId: number) {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (data: { title?: string; body?: string }) =>
|
||||
api.patch<PullRequest>(`/api/v1/repos/${owner}/${repo}/pulls/${prId}`, prSchema, data),
|
||||
onSuccess: (updated) => {
|
||||
queryClient.setQueryData(['repos', owner, repo, 'pulls', prId], updated)
|
||||
queryClient.invalidateQueries({ queryKey: ['repos', owner, repo, 'pulls'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useReopenPR(owner: string, repo: string) {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (prId: number) =>
|
||||
api.post(`/api/v1/repos/${owner}/${repo}/pulls/${prId}/merge`, mergeResponseSchema, {}),
|
||||
api.post<PullRequest>(`/api/v1/repos/${owner}/${repo}/pulls/${prId}/reopen`, prSchema, {}),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['repos', owner, repo, 'pulls'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useCreatePR(owner: string, repo: string) {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (data: { title: string; body: string; sourceBranch: string; targetBranch: string }) =>
|
||||
api.post<PullRequest>(`/api/v1/repos/${owner}/${repo}/pulls`, prSchema, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['repos', owner, repo, 'pulls'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useMergePR(owner: string, repo: string) {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: ({ prId, strategy }: { prId: number; strategy: string }) =>
|
||||
api.post(`/api/v1/repos/${owner}/${repo}/pulls/${prId}/merge`, mergeResponseSchema, { strategy }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['repos', owner, repo, 'pulls'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useClosePR(owner: string, repo: string) {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (prId: number) =>
|
||||
api.post<PullRequest>(`/api/v1/repos/${owner}/${repo}/pulls/${prId}/close`, prSchema, {}),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['repos', owner, repo, 'pulls'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user