initial completion

This commit is contained in:
2026-05-07 15:51:38 +02:00
parent f211cfc7db
commit 3b1368e16d
36 changed files with 1576 additions and 6 deletions
+205 -1
View File
@@ -9,6 +9,11 @@ import {
useMergeStrategies, useUpdateMergeStrategies,
useWebhooks, useCreateWebhook, useUpdateWebhook, useDeleteWebhook, useTestWebhook,
} from '../api/queries/workflow'
import {
useDefaultReviewers, useAddDefaultReviewer, useRemoveDefaultReviewer,
useDefaultDescription, useUpdateDefaultDescription,
useExcludedFiles, useUpdateExcludedFiles,
} from '../api/queries/prs'
import { useRecentRepos } from '../hooks/useRecentRepos'
import { useAuth } from '../contexts/AuthContext'
import { Skeleton } from '../ui/Skeleton'
@@ -145,8 +150,12 @@ export default function RepoSettingsPage() {
{section === 'branching-model' && <BranchingModelSection owner={owner} repo={repoName} />}
{section === 'merge-strategies' && <MergeStrategiesSection owner={owner} repo={repoName} />}
{section === 'webhooks' && <WebhooksSection owner={owner} repo={repoName} />}
{section === 'default-reviewers' && <DefaultReviewersSection owner={owner} repo={repoName} />}
{section === 'default-description' && <DefaultDescriptionSection owner={owner} repo={repoName} />}
{section === 'excluded-files' && <ExcludedFilesSection owner={owner} repo={repoName} />}
{!['repository-details','repository-permissions','access-keys','access-tokens',
'branch-restrictions','branching-model','merge-strategies','webhooks'].includes(section) && <ComingSoon sectionId={section} />}
'branch-restrictions','branching-model','merge-strategies','webhooks',
'default-reviewers','default-description','excluded-files'].includes(section) && <ComingSoon sectionId={section} />}
</main>
</div>
)
@@ -1476,6 +1485,201 @@ function WebhooksSection({ owner, repo }: { owner: string; repo: string }) {
)
}
// ─── Default reviewers ────────────────────────────────────────────────────────
function DefaultReviewersSection({ owner, repo }: { owner: string; repo: string }) {
const { data: reviewers = [], isLoading } = useDefaultReviewers(owner, repo)
const addReviewer = useAddDefaultReviewer(owner, repo)
const removeReviewer = useRemoveDefaultReviewer(owner, repo)
const [username, setUsername] = useState('')
const [error, setError] = useState('')
async function handleAdd(e: React.FormEvent) {
e.preventDefault()
setError('')
const u = username.trim()
if (!u) return
try {
await addReviewer.mutateAsync(u)
setUsername('')
} catch (err: any) {
setError(err.message ?? 'Failed to add reviewer')
}
}
return (
<div className="max-w-2xl px-6 py-6 space-y-6">
<div>
<h1 className="text-xl font-semibold text-[var(--c-text)]">Default reviewers</h1>
<p className="text-sm text-[var(--c-muted)] mt-1">These users are automatically added as reviewers whenever a pull request is opened in this repository.</p>
</div>
<form onSubmit={handleAdd} className="flex gap-2">
<input
value={username}
onChange={e => setUsername(e.target.value)}
placeholder="Username"
className="flex-1 border border-[var(--c-border)] rounded px-3 py-2 text-sm bg-[var(--c-surface)] text-[var(--c-text)] focus:outline-none focus:border-[var(--c-brand-focus)]"
/>
<button
type="submit"
disabled={addReviewer.isPending || !username.trim()}
className="px-4 py-2 rounded bg-[var(--c-brand)] text-white text-sm font-medium hover:bg-[var(--c-brand-hover)] disabled:opacity-50"
>
{addReviewer.isPending ? 'Adding' : 'Add reviewer'}
</button>
</form>
{error && <p className="text-xs text-[var(--c-danger)]">{error}</p>}
<div className="border border-[var(--c-border)] rounded-lg overflow-hidden">
{isLoading ? (
<div className="p-4 space-y-3">
{[0,1,2].map(i => <Skeleton key={i} className="h-10 rounded" />)}
</div>
) : reviewers.length === 0 ? (
<div className="p-8 text-center text-sm text-[var(--c-muted)]">No default reviewers configured.</div>
) : (
<ul className="divide-y divide-[var(--c-border)]">
{reviewers.map(rv => (
<li key={rv.userId} className="flex items-center justify-between px-4 py-3">
<div className="flex items-center gap-3">
{rv.avatarUrl ? (
<img src={rv.avatarUrl} alt={rv.username} className="w-8 h-8 rounded-full object-cover" />
) : (
<div className="w-8 h-8 rounded-full bg-[var(--c-brand)] flex items-center justify-center text-white text-xs font-semibold">
{rv.username.slice(0, 1).toUpperCase()}
</div>
)}
<span className="text-sm font-medium text-[var(--c-text)]">{rv.username}</span>
</div>
<button
onClick={() => removeReviewer.mutate(rv.username)}
disabled={removeReviewer.isPending}
className="text-xs text-[var(--c-danger)] hover:underline disabled:opacity-50"
>
Remove
</button>
</li>
))}
</ul>
)}
</div>
</div>
)
}
// ─── Default description ──────────────────────────────────────────────────────
function DefaultDescriptionSection({ owner, repo }: { owner: string; repo: string }) {
const { data, isLoading } = useDefaultDescription(owner, repo)
const updateDesc = useUpdateDefaultDescription(owner, repo)
const [template, setTemplate] = useState('')
const [saved, setSaved] = useState(false)
useEffect(() => {
if (data) setTemplate(data.template ?? '')
}, [data])
async function handleSave(e: React.FormEvent) {
e.preventDefault()
await updateDesc.mutateAsync(template)
setSaved(true)
setTimeout(() => setSaved(false), 2000)
}
return (
<div className="max-w-2xl px-6 py-6 space-y-6">
<div>
<h1 className="text-xl font-semibold text-[var(--c-text)]">Default description</h1>
<p className="text-sm text-[var(--c-muted)] mt-1">This template pre-fills the pull request body when a new PR is opened. Supports Markdown.</p>
</div>
{isLoading ? (
<Skeleton className="h-48 rounded" />
) : (
<form onSubmit={handleSave} className="space-y-4">
<textarea
value={template}
onChange={e => setTemplate(e.target.value)}
rows={12}
placeholder={'## Summary\n\n## Test plan\n\n## Screenshots'}
className="w-full border border-[var(--c-border)] rounded px-3 py-2.5 text-sm font-mono bg-[var(--c-surface)] text-[var(--c-text)] focus:outline-none focus:border-[var(--c-brand-focus)] resize-y"
/>
<div className="flex items-center gap-3">
<button
type="submit"
disabled={updateDesc.isPending}
className="px-4 py-2 rounded bg-[var(--c-brand)] text-white text-sm font-medium hover:bg-[var(--c-brand-hover)] disabled:opacity-50"
>
{updateDesc.isPending ? 'Saving' : 'Save template'}
</button>
{saved && <span className="text-xs text-[var(--c-success)]">Saved</span>}
</div>
</form>
)}
</div>
)
}
// ─── Excluded files ───────────────────────────────────────────────────────────
function ExcludedFilesSection({ owner, repo }: { owner: string; repo: string }) {
const { data, isLoading } = useExcludedFiles(owner, repo)
const updateFiles = useUpdateExcludedFiles(owner, repo)
const [patterns, setPatterns] = useState('')
const [saved, setSaved] = useState(false)
useEffect(() => {
if (data) setPatterns(data.patterns ?? '')
}, [data])
async function handleSave(e: React.FormEvent) {
e.preventDefault()
await updateFiles.mutateAsync(patterns)
setSaved(true)
setTimeout(() => setSaved(false), 2000)
}
return (
<div className="max-w-2xl px-6 py-6 space-y-6">
<div>
<h1 className="text-xl font-semibold text-[var(--c-text)]">Excluded files</h1>
<p className="text-sm text-[var(--c-muted)] mt-1">
Files matching these glob patterns are excluded from pull request diff views. One pattern per line.
Example: <code className="text-xs bg-[var(--c-surface-muted)] px-1 py-0.5 rounded font-mono">package-lock.json</code>
</p>
</div>
{isLoading ? (
<Skeleton className="h-48 rounded" />
) : (
<form onSubmit={handleSave} className="space-y-4">
<textarea
value={patterns}
onChange={e => setPatterns(e.target.value)}
rows={10}
placeholder={'package-lock.json\nyarn.lock\ndist/**\n*.min.js'}
className="w-full border border-[var(--c-border)] rounded px-3 py-2.5 text-sm font-mono bg-[var(--c-surface)] text-[var(--c-text)] focus:outline-none focus:border-[var(--c-brand-focus)] resize-y"
/>
<div className="text-xs text-[var(--c-muted)]">
Patterns use glob syntax. <code className="font-mono">*</code> matches any file, <code className="font-mono">**</code> matches any path segment.
</div>
<div className="flex items-center gap-3">
<button
type="submit"
disabled={updateFiles.isPending}
className="px-4 py-2 rounded bg-[var(--c-brand)] text-white text-sm font-medium hover:bg-[var(--c-brand-hover)] disabled:opacity-50"
>
{updateFiles.isPending ? 'Saving' : 'Save patterns'}
</button>
{saved && <span className="text-xs text-[var(--c-success)]">Saved</span>}
</div>
</form>
)}
</div>
)
}
// ─── Coming soon ──────────────────────────────────────────────────────────────
function ComingSoon({ sectionId }: { sectionId: SectionId }) {