Conversation
Adds a new /orgs page to the admin dashboard that displays a table of all org-level GitHub repositories with commit stats and submodule installation counts, backed by GET /api/admins/sandboxes/orgs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughIntroduces a new Org Repos feature enabling admins to view and sort organization repositories. Adds app/orgs route with OrgsPage component, OrgsTable with sortable columns, useAdminOrgs hook for authenticated data fetching, and OrgsNavButton navigation element. Includes TypeScript interfaces for org repository metadata and API responses. Changes
Sequence DiagramsequenceDiagram
actor User
participant OrgsPage as OrgsPage Component
participant OrgsTableContainer as OrgsTableContainer<br/>(Client)
participant useAdminOrgs as useAdminOrgs Hook
participant Privy as Privy Auth
participant Query as TanStack Query
participant API as /api/admins/sandboxes/orgs
participant OrgsTable as OrgsTable & Columns
User->>OrgsPage: Navigate to /orgs
OrgsPage->>OrgsTableContainer: Render
OrgsTableContainer->>useAdminOrgs: Call hook
useAdminOrgs->>Privy: Get access token
Privy-->>useAdminOrgs: Return token
useAdminOrgs->>Query: Execute query with fetchAdminOrgs
Query->>API: GET with auth header
API-->>Query: Return org repos data
Query-->>useAdminOrgs: Return data/loading/error state
useAdminOrgs-->>OrgsTableContainer: repos array
OrgsTableContainer->>OrgsTable: Pass repos data
OrgsTable->>OrgsTable: Initialize sorting (latest_commit_at desc)
OrgsTable-->>User: Render sortable table with columns
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/orgs/page.tsx`:
- Around line 1-4: The /orgs route currently renders OrgsPage directly; wrap it
with the same client-side admin/auth guard used elsewhere by creating a client
component (e.g., OrgsContent) that imports usePrivy and useIsAdmin, checks
ready/authenticated and isAdmin (returning null or a loading skeleton while
ready/isLoading, and an access-denied UI when not authenticated or not admin),
and then renders OrgsPage; update the default export in app/orgs/page.tsx to
render OrgsContent instead of OrgsPage so the route enforces authenticated +
isAdmin gating.
In `@components/Orgs/orgsColumns.tsx`:
- Around line 13-15: The anchor element in orgsColumns.tsx that uses
target="_blank" currently sets rel="noreferrer" — update this to include
noopener (e.g., rel="noreferrer noopener") to explicitly prevent opener access;
locate the anchor with attributes target="_blank" and className="flex
items-center gap-1 font-medium hover:underline" and modify its rel value
accordingly.
In `@hooks/useAdminOrgs.ts`:
- Around line 12-16: The query cache key in useAdminOrgs is currently static
("admin","orgs") which can leak data across account switches; update
useAdminOrgs to include the current user's ID from usePrivy (e.g., queryKey:
["admin","orgs", user.id]) and change the enabled condition to wait for the user
id (e.g., enabled: ready && authenticated && !!user?.id); apply the same fix to
useAdminSandboxes (replace its static queryKey with ["admin","sandboxes",
user.id] and guard its enabled flag on user.id) so cache entries are isolated
per authenticated user.
In `@lib/fetchAdminOrgs.ts`:
- Around line 21-22: The code in lib/fetchAdminOrgs.ts returns data.repos from
the parsed JSON without checking the API-level status field; update the success
path in the function that calls res.json() (the variable named data) to validate
data.status (or equivalent) and treat non-success values as errors: if
data.status !== 'success' (or if data.error/message exists) throw or return an
explicit error instead of silently returning data.repos ?? []; ensure the thrown
error includes the API error message/details so callers can surface the failure.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f8bf6c1c-3076-48d4-b853-8ec2f7a1af1d
📒 Files selected for processing (10)
app/orgs/page.tsxcomponents/Home/AdminDashboard.tsxcomponents/Home/OrgsNavButton.tsxcomponents/Orgs/OrgsPage.tsxcomponents/Orgs/OrgsTable.tsxcomponents/Orgs/OrgsTableContainer.tsxcomponents/Orgs/orgsColumns.tsxhooks/useAdminOrgs.tslib/fetchAdminOrgs.tstypes/org.ts
| import OrgsPage from "@/components/Orgs/OrgsPage"; | ||
|
|
||
| export default function Page() { | ||
| return <OrgsPage />; |
There was a problem hiding this comment.
Add the same admin/auth guard used by other admin surfaces.
This route renders admin content directly, while components/Home/HomeContent.tsx (Lines 1-38) enforces authenticated + isAdmin checks. /orgs should apply equivalent gating to prevent unprivileged users from accessing the page shell.
🔐 Suggested route-level adjustment
-import OrgsPage from "@/components/Orgs/OrgsPage";
+import OrgsContent from "@/components/Orgs/OrgsContent";
export default function Page() {
- return <OrgsPage />;
+ return <OrgsContent />;
}// components/Orgs/OrgsContent.tsx
"use client";
import { usePrivy } from "@privy-io/react-auth";
import { useIsAdmin } from "@/hooks/useIsAdmin";
import OrgsPage from "@/components/Orgs/OrgsPage";
export default function OrgsContent() {
const { ready, authenticated } = usePrivy();
const { data: isAdmin, isLoading } = useIsAdmin();
if (!ready || isLoading) return null; // or skeleton
if (!authenticated || !isAdmin) return <div>Access Denied</div>;
return <OrgsPage />;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/orgs/page.tsx` around lines 1 - 4, The /orgs route currently renders
OrgsPage directly; wrap it with the same client-side admin/auth guard used
elsewhere by creating a client component (e.g., OrgsContent) that imports
usePrivy and useIsAdmin, checks ready/authenticated and isAdmin (returning null
or a loading skeleton while ready/isLoading, and an access-denied UI when not
authenticated or not admin), and then renders OrgsPage; update the default
export in app/orgs/page.tsx to render OrgsContent instead of OrgsPage so the
route enforces authenticated + isAdmin gating.
| target="_blank" | ||
| rel="noreferrer" | ||
| className="flex items-center gap-1 font-medium hover:underline" |
There was a problem hiding this comment.
Harden external-link opener isolation.
For target="_blank" links, include noopener in rel to explicitly prevent opener access.
💡 Suggested fix
- rel="noreferrer"
+ rel="noopener noreferrer"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| target="_blank" | |
| rel="noreferrer" | |
| className="flex items-center gap-1 font-medium hover:underline" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="flex items-center gap-1 font-medium hover:underline" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/Orgs/orgsColumns.tsx` around lines 13 - 15, The anchor element in
orgsColumns.tsx that uses target="_blank" currently sets rel="noreferrer" —
update this to include noopener (e.g., rel="noreferrer noopener") to explicitly
prevent opener access; locate the anchor with attributes target="_blank" and
className="flex items-center gap-1 font-medium hover:underline" and modify its
rel value accordingly.
| const { ready, authenticated, getAccessToken } = usePrivy(); | ||
|
|
||
| return useQuery({ | ||
| queryKey: ["admin", "orgs"], | ||
| queryFn: async () => { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== React Query version(s) =="
fd package.json --exec jq -r '.dependencies["@tanstack/react-query"] // .devDependencies["@tanstack/react-query"] // empty' {}
echo
echo "== Privy hook usage patterns =="
rg -n -C2 '\busePrivy\(' --type=ts --type=tsx
echo
echo "== Admin query key patterns =="
rg -n -C2 'queryKey:\s*\["admin"' --type=ts --type=tsxRepository: recoupable/admin
Length of output: 174
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# First, find and display the useAdminOrgs.ts file
echo "== Content of hooks/useAdminOrgs.ts =="
find . -name "useAdminOrgs.ts" -type f -exec cat -n {} +
echo
echo "== Privy hook usage patterns (fixed) =="
rg -n -C2 'usePrivy' --type ts
echo
echo "== Admin query key patterns =="
rg -n -C2 'queryKey.*admin' --type tsRepository: recoupable/admin
Length of output: 4473
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check useAdminSandboxes which was mentioned in search results
echo "== Content of hooks/useAdminSandboxes.ts =="
cat -n hooks/useAdminSandboxes.ts
echo
echo "== Content of hooks/useIsAdmin.ts =="
cat -n hooks/useIsAdmin.tsRepository: recoupable/admin
Length of output: 2034
Scope the query cache key by authenticated user.
The static queryKey: ["admin", "orgs"] without a user identifier can leak cached org data across account switches in the same client session. Since usePrivy() exposes a user property (as seen in other components), include the user ID in the queryKey to isolate cache entries per user. Update the enabled condition to ensure the query doesn't run until the user ID is available.
Suggested fix
export function useAdminOrgs() {
- const { ready, authenticated, getAccessToken } = usePrivy();
+ const { ready, authenticated, user, getAccessToken } = usePrivy();
return useQuery({
- queryKey: ["admin", "orgs"],
+ queryKey: ["admin", "orgs", user?.id],
queryFn: async () => {
const token = await getAccessToken();
if (!token) throw new Error("Not authenticated");
return fetchAdminOrgs(token);
},
- enabled: ready && authenticated,
+ enabled: ready && authenticated && !!user?.id,
});
}Similar patterns exist in useAdminSandboxes (lines 15, 21) and should receive the same treatment.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@hooks/useAdminOrgs.ts` around lines 12 - 16, The query cache key in
useAdminOrgs is currently static ("admin","orgs") which can leak data across
account switches; update useAdminOrgs to include the current user's ID from
usePrivy (e.g., queryKey: ["admin","orgs", user.id]) and change the enabled
condition to wait for the user id (e.g., enabled: ready && authenticated &&
!!user?.id); apply the same fix to useAdminSandboxes (replace its static
queryKey with ["admin","sandboxes", user.id] and guard its enabled flag on
user.id) so cache entries are isolated per authenticated user.
| const data = await res.json(); | ||
| return data.repos ?? []; |
There was a problem hiding this comment.
Handle logical API errors in successful HTTP responses.
The current success path ignores status from the response body. If the backend returns { status: "error" } with HTTP 200, this will silently return an empty list instead of surfacing the failure.
💡 Suggested fix
import { API_BASE_URL } from "@/lib/consts";
-import type { OrgRepoRow } from "@/types/org";
+import type { AdminOrgsResponse, OrgRepoRow } from "@/types/org";
@@
- const data = await res.json();
- return data.repos ?? [];
+ const data = (await res.json()) as AdminOrgsResponse & { message?: string };
+ if (data.status !== "success") {
+ throw new Error(data.message ?? "Failed to load org repos");
+ }
+ return Array.isArray(data.repos) ? data.repos : [];
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/fetchAdminOrgs.ts` around lines 21 - 22, The code in
lib/fetchAdminOrgs.ts returns data.repos from the parsed JSON without checking
the API-level status field; update the success path in the function that calls
res.json() (the variable named data) to validate data.status (or equivalent) and
treat non-success values as errors: if data.status !== 'success' (or if
data.error/message exists) throw or return an explicit error instead of silently
returning data.repos ?? []; ensure the thrown error includes the API error
message/details so callers can surface the failure.
Summary
/orgspage to the admin dashboard displaying a table of all org-level GitHub reposGET /api/admins/sandboxes/orgsvia React Query + Privy authTest plan
🤖 Generated with Claude Code
Summary by CodeRabbit