diff --git a/apps/web/app/[locale]/claim/page.tsx b/apps/web/app/[locale]/claim/page.tsx index 8a6cfe4..8fddab3 100644 --- a/apps/web/app/[locale]/claim/page.tsx +++ b/apps/web/app/[locale]/claim/page.tsx @@ -64,6 +64,8 @@ export default async function ClaimPage({ success: { title: t('success.title'), description: t('success.description'), + pendingTitle: t('success.pendingTitle'), + pendingDescription: t('success.pendingDescription'), viewRequests: t('success.viewRequests'), }, addSuccess: { diff --git a/apps/web/app/[locale]/layout.tsx b/apps/web/app/[locale]/layout.tsx index 8d73b20..8ab1b18 100644 --- a/apps/web/app/[locale]/layout.tsx +++ b/apps/web/app/[locale]/layout.tsx @@ -6,6 +6,7 @@ import { locales, localeDirection, type Locale } from '@/i18n'; import { Providers } from '../providers'; import { Suspense } from 'react'; import { QueryNotification } from '@/components/QueryNotification'; +import { ProgressBar } from '@/components/ProgressBar'; import '../globals.css'; export async function generateMetadata({ @@ -70,6 +71,7 @@ export default async function LocaleLayout({ + {children} diff --git a/apps/web/app/api/skills/add-request/route.ts b/apps/web/app/api/skills/add-request/route.ts index 286431f..e674f2f 100644 --- a/apps/web/app/api/skills/add-request/route.ts +++ b/apps/web/app/api/skills/add-request/route.ts @@ -1,7 +1,7 @@ import type { NextRequest } from 'next/server'; import { NextResponse } from 'next/server'; import { auth } from '@/lib/auth'; -import { createDb, addRequestQueries, userQueries } from '@skillhub/db'; +import { createDb, addRequestQueries, discoveredRepoQueries, userQueries } from '@skillhub/db'; import { sanitizeReason } from '@/lib/sanitize'; import { sendClaimSubmittedEmail } from '@/lib/email'; @@ -389,6 +389,26 @@ export async function POST(request: NextRequest) { hasSkillMd: validation.hasSkillMd, }); + // Auto-approve and queue for crawling if SKILL.md found + if (validation.hasSkillMd && validation.skillPaths.length > 0) { + await addRequestQueries.updateStatus(db, requestId, { + status: 'approved', + }); + + // Queue repo for indexer crawling via discovered_repos + try { + await discoveredRepoQueries.upsert(db, { + id: `${parsed.owner}/${parsed.repo}`, + owner: parsed.owner, + repo: parsed.repo, + discoveredVia: 'add-request', + githubStars: 0, + }); + } catch (err) { + console.warn('[Claim] Failed to queue repo for crawling:', err); + } + } + // Build appropriate response message let message: string; if (validation.skillPaths.length > 1) { diff --git a/apps/web/app/api/skills/removal-request/route.ts b/apps/web/app/api/skills/removal-request/route.ts index 80c7782..bc74ff3 100644 --- a/apps/web/app/api/skills/removal-request/route.ts +++ b/apps/web/app/api/skills/removal-request/route.ts @@ -124,6 +124,7 @@ export async function POST(request: NextRequest) { // Check repository ownership using public API (no token needed for public repos) let isOwner = false; + let repoDeleted = false; let githubError: string | null = null; try { @@ -144,7 +145,7 @@ export async function POST(request: NextRequest) { isOwner = true; } } else if (repoResponse.status === 404) { - githubError = 'Repository not found on GitHub'; + repoDeleted = true; } else if (repoResponse.status === 403) { githubError = 'GitHub API rate limit exceeded'; } @@ -160,23 +161,34 @@ export async function POST(request: NextRequest) { ); } - if (!isOwner) { + if (!isOwner && !repoDeleted) { return NextResponse.json( { error: 'You are not the owner of this repository', code: 'NOT_OWNER' }, { status: 403 } ); } - // Create the removal request (auto-approved since owner is verified) + // Create the removal request const sanitizedReason = reason ? sanitizeReason(reason) : 'No reason provided'; const requestId = await removalRequestQueries.create(db, { userId: dbUser.id, skillId, reason: sanitizedReason, - verifiedOwner: true, + verifiedOwner: !repoDeleted, }); - // Auto-approve: Block the skill from being re-indexed + if (repoDeleted) { + // Repo not found — submit for admin review instead of auto-approving + return NextResponse.json({ + success: true, + requestId, + pending: true, + blocked: false, + message: 'Repository not found on GitHub. Your removal request has been submitted for admin review.', + }); + } + + // Owner verified — auto-approve: Block the skill from being re-indexed await skillQueries.block(db, skillId); // Update the request status to approved diff --git a/apps/web/components/ClaimForm.tsx b/apps/web/components/ClaimForm.tsx index cbde2a8..2100a2e 100644 --- a/apps/web/components/ClaimForm.tsx +++ b/apps/web/components/ClaimForm.tsx @@ -55,6 +55,8 @@ interface ClaimFormProps { success: { title: string; description: string; + pendingTitle: string; + pendingDescription: string; viewRequests: string; }; addSuccess: { @@ -161,6 +163,7 @@ export function ClaimForm({ translations }: ClaimFormProps) { const [isSubmittingRemove, setIsSubmittingRemove] = useState(false); const [removeError, setRemoveError] = useState(''); const [removeSuccess, setRemoveSuccess] = useState(false); + const [removePending, setRemovePending] = useState(false); const [removalRequests, setRemovalRequests] = useState([]); const [loadingRemovalRequests, setLoadingRemovalRequests] = useState(false); @@ -267,6 +270,7 @@ export function ClaimForm({ translations }: ClaimFormProps) { } setRemoveSuccess(true); + setRemovePending(data.pending || false); setSkillId(''); setRemoveReason(''); fetchRemovalRequests(); @@ -438,11 +442,13 @@ export function ClaimForm({ translations }: ClaimFormProps) {

- {translations.success.title} + {removePending ? translations.success.pendingTitle : translations.success.title}

-

{translations.success.description}

+

+ {removePending ? translations.success.pendingDescription : translations.success.description} +