feat: add public transparency page (#86)#89
Conversation
|
@Sendi0011 is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
|
No actionable comments were generated in the recent review. 🎉 📝 WalkthroughWalkthroughAdds a public Transparency dashboard: a client page that displays platform stats and recent payouts, two new App Router API GET routes returning placeholder data, a typed API client and Zod schemas, React Query hooks for fetching stats/payouts, and a navbar link to /transparency. Changes
Sequence DiagramsequenceDiagram
participant Browser as Browser
participant Page as TransparencyPage
participant Hooks as ReactQueryHooks
participant ApiClient as transparencyApi
participant Routes as API Routes
participant Memory as In-File Data
Browser->>Page: Navigate to /transparency
Page->>Hooks: usePlatformStats()
Hooks->>ApiClient: getStats()
ApiClient->>Routes: GET /api/transparency/stats
Routes->>Memory: read placeholder stats
Memory-->>Routes: stats JSON
Routes-->>ApiClient: respond JSON
ApiClient-->>Hooks: PlatformStats
Hooks-->>Page: stats (data/loading/error)
Page->>Hooks: useRecentPayouts(limit)
Hooks->>ApiClient: getRecentPayouts(limit)
ApiClient->>Routes: GET /api/transparency/payouts?limit=X
Routes->>Memory: read payouts array
Memory-->>Routes: payouts slice
Routes-->>ApiClient: respond JSON
ApiClient-->>Hooks: RecentPayout[]
Hooks-->>Page: payouts (data/loading/error)
Page->>Browser: render StatCards & PayoutRows
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. 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
🧹 Nitpick comments (2)
app/transparency/page.tsx (1)
68-70:toLocaleDateString()without a locale produces inconsistent output across users.For a public financial transparency feed, date formatting should be deterministic. Consider passing an explicit locale and options:
♻️ Proposed fix
- {new Date(payout.paidAt).toLocaleDateString()} + {new Date(payout.paidAt).toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric', + })}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/transparency/page.tsx` around lines 68 - 70, The current use of new Date(payout.paidAt).toLocaleDateString() in the span produces non-deterministic dates across user locales; replace it with an explicit locale and formatting options (or use Intl.DateTimeFormat) so the public payout date is deterministic—update the expression in the span that references new Date(payout.paidAt).toLocaleDateString() to format with a fixed locale (e.g. "en-US" or "en-GB") and explicit options like year/month/day (or instantiate a shared Intl.DateTimeFormat) to ensure consistent output.app/api/transparency/payouts/route.ts (1)
8-16: Inline type duplicatesRecentPayout— extract to a shared types file.The inline object type at lines 8–16 replicates the shape already defined in
lib/api/transparency.ts(recentPayoutSchema/RecentPayout). Importing the client-side fetch module into a server route is not ideal, but maintaining a parallel type in the route creates a silent drift risk: if the schema changes, TypeScript won't catch the mismatch here. Consider extracting the shared shape into a neutraltypes/transparency.ts(no fetch dependency) and importing it in bothlib/api/transparency.tsand this route.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/transparency/payouts/route.ts` around lines 8 - 16, The route declares an inline payouts type that duplicates RecentPayout/recentPayoutSchema; extract a neutral shared type (e.g., export interface RecentPayout) into a small types module (types/transparency.ts) with the same shape used by recentPayoutSchema, export it, then import that RecentPayout type into both this route (replace the inline annotation on payouts) and the module that defines recentPayoutSchema (use the shared type there) so the shape is single-sourced and won't silently drift; ensure the new types file has no client fetch dependencies and adjust imports accordingly.
🤖 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/api/transparency/payouts/route.ts`:
- Line 5: The limit parsed from searchParams (const limit) must be constrained
to a safe maximum to prevent very large queries; update the parsing to coerce to
an integer, handle NaN/negative values, and clamp it with a defined max (e.g.,
const MAX_LIMIT = 100) using Math.min/Math.max so limit = clamp(parsed, 1,
MAX_LIMIT); apply this change where const limit is defined in route.ts (the
request handler that reads searchParams.get("limit")).
In `@app/transparency/page.tsx`:
- Around line 137-156: The code is casting statsErr to Error redundantly; update
the AlertDescription rendering to remove the unnecessary cast and use the
existing typed variable directly (replace {(statsErr as Error)?.message} with
statsErr?.message) in the component that shows the error (inside the JSX block
rendering Alert / AlertDescription), ensuring you still handle possible null by
using optional chaining; no other changes to refetchStats or the Alert structure
are required.
- Around line 15-43: StatCard uses the React type namespace (React.ElementType)
but the file does not import React, causing a TS error; fix by importing the
type instead of the whole namespace—add an import type { ElementType } from
'react' at the top and change the prop type to use ElementType (or alternatively
add import React from 'react' if you prefer), and apply the same change to the
other component that references React.ElementType (the tier badge component) so
both compile under the "react-jsx" tsconfig.
In `@lib/api/transparency.ts`:
- Around line 27-33: The transparencyApi methods currently return axios data
without runtime validation; update getStats and getRecentPayouts to validate the
response with the Zod schemas before returning (e.g., import your
PlatformStatsSchema and RecentPayoutSchema and call
PlatformStatsSchema.parse(...) on the result of get in getStats, and
RecentPayoutSchema.array().parse(...) in getRecentPayouts), or alternately
extend the get helper to accept a validator and run parse inside it; target the
transparencyApi.getStats and transparencyApi.getRecentPayouts calls to ensure
parsed/validated data (use the TRANSPARENCY_ENDPOINT and existing
PlatformStats/RecentPayout types as references).
---
Nitpick comments:
In `@app/api/transparency/payouts/route.ts`:
- Around line 8-16: The route declares an inline payouts type that duplicates
RecentPayout/recentPayoutSchema; extract a neutral shared type (e.g., export
interface RecentPayout) into a small types module (types/transparency.ts) with
the same shape used by recentPayoutSchema, export it, then import that
RecentPayout type into both this route (replace the inline annotation on
payouts) and the module that defines recentPayoutSchema (use the shared type
there) so the shape is single-sourced and won't silently drift; ensure the new
types file has no client fetch dependencies and adjust imports accordingly.
In `@app/transparency/page.tsx`:
- Around line 68-70: The current use of new
Date(payout.paidAt).toLocaleDateString() in the span produces non-deterministic
dates across user locales; replace it with an explicit locale and formatting
options (or use Intl.DateTimeFormat) so the public payout date is
deterministic—update the expression in the span that references new
Date(payout.paidAt).toLocaleDateString() to format with a fixed locale (e.g.
"en-US" or "en-GB") and explicit options like year/month/day (or instantiate a
shared Intl.DateTimeFormat) to ensure consistent output.
|
|
||
| export async function GET(request: NextRequest) { | ||
| const { searchParams } = new URL(request.url); | ||
| const limit = Number(searchParams.get("limit")) || 10; |
There was a problem hiding this comment.
Add an upper bound to limit before wiring in real DB queries.
Number(searchParams.get("limit")) || 10 accepts arbitrarily large values. A caller can pass limit=1000000, which will become a full-table scan once the TODO is resolved with a real query. Cap it explicitly:
🛡️ Proposed fix
- const limit = Number(searchParams.get("limit")) || 10;
+ const raw = Number(searchParams.get("limit"));
+ const limit = raw > 0 && Number.isFinite(raw) ? Math.min(raw, 100) : 10;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/api/transparency/payouts/route.ts` at line 5, The limit parsed from
searchParams (const limit) must be constrained to a safe maximum to prevent very
large queries; update the parsing to coerce to an integer, handle NaN/negative
values, and clamp it with a defined max (e.g., const MAX_LIMIT = 100) using
Math.min/Math.max so limit = clamp(parsed, 1, MAX_LIMIT); apply this change
where const limit is defined in route.ts (the request handler that reads
searchParams.get("limit")).
| function StatCard({ | ||
| title, | ||
| value, | ||
| icon: Icon, | ||
| isLoading, | ||
| }: { | ||
| title: string; | ||
| value: string; | ||
| icon: React.ElementType; | ||
| isLoading: boolean; | ||
| }) { | ||
| return ( | ||
| <Card> | ||
| <CardHeader className="flex flex-row items-center justify-between pb-2"> | ||
| <CardTitle className="text-sm font-medium text-muted-foreground"> | ||
| {title} | ||
| </CardTitle> | ||
| <Icon className="h-4 w-4 text-muted-foreground" /> | ||
| </CardHeader> | ||
| <CardContent> | ||
| {isLoading ? ( | ||
| <Skeleton className="h-8 w-32" /> | ||
| ) : ( | ||
| <p className="text-2xl font-bold text-foreground">{value}</p> | ||
| )} | ||
| </CardContent> | ||
| </Card> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check the actual file to see imports and full context
cat -n app/transparency/page.tsx | head -50Repository: boundlessfi/bounties
Length of output: 2103
🏁 Script executed:
# Check tsconfig.json for jsx and allowUmdGlobalAccess settings
fd -e json "tsconfig" --max-depth 2 -x cat {}Repository: boundlessfi/bounties
Length of output: 729
🏁 Script executed:
# Look for global.d.ts files that might define React globally
fd -e "d.ts" "global" --max-depth 3Repository: boundlessfi/bounties
Length of output: 46
🏁 Script executed:
# Search for other uses of React.ElementType in the codebase
rg "React\.(ElementType|FC|ReactNode)" --type=tsx -A 2 -B 2 | head -100Repository: boundlessfi/bounties
Length of output: 91
🏁 Script executed:
# Search for React.ElementType or React. type references (using proper rg syntax)
rg "React\." app/transparency/page.tsx -A 1 -B 1Repository: boundlessfi/bounties
Length of output: 135
🏁 Script executed:
# Look for how other components define icon/component props
rg "icon:|Icon" --type ts --type tsx -A 2 -B 2 | head -150Repository: boundlessfi/bounties
Length of output: 91
🏁 Script executed:
# Check if React is imported anywhere in the codebase for type usage
rg "import.*React" --type tsx | grep -i "elementtype\|React\.FC\|React\.ReactNode" | head -20Repository: boundlessfi/bounties
Length of output: 91
🏁 Script executed:
# Look for ElementType imports to see the pattern used elsewhere
rg "ElementType" --type tsx -B 3 | head -50Repository: boundlessfi/bounties
Length of output: 91
🏁 Script executed:
# Search for React. references in tsx files using glob pattern
rg "React\." -g "*.tsx" -A 1 -B 1 | head -100Repository: boundlessfi/bounties
Length of output: 4260
🏁 Script executed:
# Search for how icon/Icon props are typed in other components
rg "icon.*:" -g "*.tsx" -A 1 | head -100Repository: boundlessfi/bounties
Length of output: 7006
🏁 Script executed:
# Check if any component file imports ElementType directly
rg "import.*ElementType" -g "*.tsx"Repository: boundlessfi/bounties
Length of output: 46
🏁 Script executed:
# Verify by checking if the file would compile - look at how components handle component types
rg "React\.FC\|React\.Component\|ElementType" -g "*.tsx" -B 2 | head -80Repository: boundlessfi/bounties
Length of output: 46
🏁 Script executed:
# Check tier-badge.tsx imports (it uses React.ElementType)
head -20 components/reputation/tier-badge.tsxRepository: boundlessfi/bounties
Length of output: 839
🏁 Script executed:
# Check a component using React.ReactNode to see if they import React
head -20 components/ui/alert-dialog.tsxRepository: boundlessfi/bounties
Length of output: 642
🏁 Script executed:
# Final check: look at full imports of app/transparency/page.tsx
head -15 app/transparency/page.tsxRepository: boundlessfi/bounties
Length of output: 720
React.ElementType used without importing React — TypeScript compile error.
With the new JSX transform, you still need to import React "in order to use Hooks or other exports that React provides" — and the same applies to type namespace references. Line 23 references React.ElementType but there is no import React from 'react' or import type { ElementType } from 'react' in the file. Under the project's tsconfig ("jsx": "react-jsx", strict: true, no allowUmdGlobalAccess), this produces a TypeScript error and will fail next build.
Note: components/reputation/tier-badge.tsx has the same issue on line 12.
🐛 Proposed fix
+import type { ElementType } from 'react';
function StatCard({
title,
value,
icon: Icon,
isLoading,
}: {
title: string;
value: string;
- icon: React.ElementType;
+ icon: ElementType;
isLoading: boolean;
}) {📝 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.
| function StatCard({ | |
| title, | |
| value, | |
| icon: Icon, | |
| isLoading, | |
| }: { | |
| title: string; | |
| value: string; | |
| icon: React.ElementType; | |
| isLoading: boolean; | |
| }) { | |
| return ( | |
| <Card> | |
| <CardHeader className="flex flex-row items-center justify-between pb-2"> | |
| <CardTitle className="text-sm font-medium text-muted-foreground"> | |
| {title} | |
| </CardTitle> | |
| <Icon className="h-4 w-4 text-muted-foreground" /> | |
| </CardHeader> | |
| <CardContent> | |
| {isLoading ? ( | |
| <Skeleton className="h-8 w-32" /> | |
| ) : ( | |
| <p className="text-2xl font-bold text-foreground">{value}</p> | |
| )} | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |
| function StatCard({ | |
| title, | |
| value, | |
| icon: Icon, | |
| isLoading, | |
| }: { | |
| title: string; | |
| value: string; | |
| icon: ElementType; | |
| isLoading: boolean; | |
| }) { | |
| return ( | |
| <Card> | |
| <CardHeader className="flex flex-row items-center justify-between pb-2"> | |
| <CardTitle className="text-sm font-medium text-muted-foreground"> | |
| {title} | |
| </CardTitle> | |
| <Icon className="h-4 w-4 text-muted-foreground" /> | |
| </CardHeader> | |
| <CardContent> | |
| {isLoading ? ( | |
| <Skeleton className="h-8 w-32" /> | |
| ) : ( | |
| <p className="text-2xl font-bold text-foreground">{value}</p> | |
| )} | |
| </CardContent> | |
| </Card> | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/transparency/page.tsx` around lines 15 - 43, StatCard uses the React type
namespace (React.ElementType) but the file does not import React, causing a TS
error; fix by importing the type instead of the whole namespace—add an import
type { ElementType } from 'react' at the top and change the prop type to use
ElementType (or alternatively add import React from 'react' if you prefer), and
apply the same change to the other component that references React.ElementType
(the tier badge component) so both compile under the "react-jsx" tsconfig.
| {statsError && ( | ||
| <Alert variant="destructive"> | ||
| <AlertCircle className="h-4 w-4" /> | ||
| <AlertTitle>Error</AlertTitle> | ||
| <AlertDescription className="flex flex-col gap-2"> | ||
| <p> | ||
| Failed to load platform stats.{" "} | ||
| {(statsErr as Error)?.message} | ||
| </p> | ||
| <Button | ||
| variant="outline" | ||
| size="sm" | ||
| onClick={() => refetchStats()} | ||
| className="w-fit bg-background text-foreground border-border hover:bg-muted" | ||
| > | ||
| Try Again | ||
| </Button> | ||
| </AlertDescription> | ||
| </Alert> | ||
| )} |
There was a problem hiding this comment.
as Error cast is redundant in TanStack Query v5.
In @tanstack/react-query v5, error is typed as Error | null by default, so statsErr is already Error | null. The as Error at line 144 is unnecessary; use statsErr?.message directly.
♻️ Proposed fix
- {(statsErr as Error)?.message}
+ {statsErr?.message}📝 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.
| {statsError && ( | |
| <Alert variant="destructive"> | |
| <AlertCircle className="h-4 w-4" /> | |
| <AlertTitle>Error</AlertTitle> | |
| <AlertDescription className="flex flex-col gap-2"> | |
| <p> | |
| Failed to load platform stats.{" "} | |
| {(statsErr as Error)?.message} | |
| </p> | |
| <Button | |
| variant="outline" | |
| size="sm" | |
| onClick={() => refetchStats()} | |
| className="w-fit bg-background text-foreground border-border hover:bg-muted" | |
| > | |
| Try Again | |
| </Button> | |
| </AlertDescription> | |
| </Alert> | |
| )} | |
| {statsError && ( | |
| <Alert variant="destructive"> | |
| <AlertCircle className="h-4 w-4" /> | |
| <AlertTitle>Error</AlertTitle> | |
| <AlertDescription className="flex flex-col gap-2"> | |
| <p> | |
| Failed to load platform stats.{" "} | |
| {statsErr?.message} | |
| </p> | |
| <Button | |
| variant="outline" | |
| size="sm" | |
| onClick={() => refetchStats()} | |
| className="w-fit bg-background text-foreground border-border hover:bg-muted" | |
| > | |
| Try Again | |
| </Button> | |
| </AlertDescription> | |
| </Alert> | |
| )} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/transparency/page.tsx` around lines 137 - 156, The code is casting
statsErr to Error redundantly; update the AlertDescription rendering to remove
the unnecessary cast and use the existing typed variable directly (replace
{(statsErr as Error)?.message} with statsErr?.message) in the component that
shows the error (inside the JSX block rendering Alert / AlertDescription),
ensuring you still handle possible null by using optional chaining; no other
changes to refetchStats or the Alert structure are required.
| export const transparencyApi = { | ||
| getStats: (): Promise<PlatformStats> => | ||
| get<PlatformStats>(`${TRANSPARENCY_ENDPOINT}/stats`), | ||
|
|
||
| getRecentPayouts: (limit = 10): Promise<RecentPayout[]> => | ||
| get<RecentPayout[]>(`${TRANSPARENCY_ENDPOINT}/payouts`, { params: { limit } }), | ||
| }; No newline at end of file |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
# First, find where the get helper is imported from in transparency.ts
head -30 lib/api/transparency.ts | grep -E "^import|^export"Repository: boundlessfi/bounties
Length of output: 372
🏁 Script executed:
# Search for the get function definition in lib/client
fd -type f -e ts lib/clientRepository: boundlessfi/bounties
Length of output: 234
🏁 Script executed:
# Also search for any client-related files
fd -type f -name "*client*" libRepository: boundlessfi/bounties
Length of output: 234
🏁 Script executed:
# Find client files in lib directory
find lib -name "*client*" -type fRepository: boundlessfi/bounties
Length of output: 143
🏁 Script executed:
# Also check the directory structure of lib/api
ls -la lib/api/Repository: boundlessfi/bounties
Length of output: 662
🏁 Script executed:
# Read the client.ts file to see the get() function implementation
cat -n lib/api/client.tsRepository: boundlessfi/bounties
Length of output: 4964
Zod schemas are defined but never used for runtime validation.
get<PlatformStats>() and get<RecentPayout[]>() accept generic type parameters, not Zod schemas — the get helper in lib/api/client.ts performs no runtime validation and simply returns the axios response data. When the stub routes are replaced with real DB queries, unexpected field types or missing fields will surface as silent runtime bugs rather than Zod parse errors. Either call .parse() in the API methods, or extend the get helper to accept a validator argument.
♻️ Proposed refactor (parse at the call site)
export const transparencyApi = {
- getStats: (): Promise<PlatformStats> =>
- get<PlatformStats>(`${TRANSPARENCY_ENDPOINT}/stats`),
+ getStats: async (): Promise<PlatformStats> => {
+ const data = await get(`${TRANSPARENCY_ENDPOINT}/stats`);
+ return platformStatsSchema.parse(data);
+ },
- getRecentPayouts: (limit = 10): Promise<RecentPayout[]> =>
- get<RecentPayout[]>(`${TRANSPARENCY_ENDPOINT}/payouts`, { params: { limit } }),
+ getRecentPayouts: async (limit = 10): Promise<RecentPayout[]> => {
+ const data = await get(`${TRANSPARENCY_ENDPOINT}/payouts`, { params: { limit } });
+ return z.array(recentPayoutSchema).parse(data);
+ },
};📝 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.
| export const transparencyApi = { | |
| getStats: (): Promise<PlatformStats> => | |
| get<PlatformStats>(`${TRANSPARENCY_ENDPOINT}/stats`), | |
| getRecentPayouts: (limit = 10): Promise<RecentPayout[]> => | |
| get<RecentPayout[]>(`${TRANSPARENCY_ENDPOINT}/payouts`, { params: { limit } }), | |
| }; | |
| export const transparencyApi = { | |
| getStats: async (): Promise<PlatformStats> => { | |
| const data = await get(`${TRANSPARENCY_ENDPOINT}/stats`); | |
| return platformStatsSchema.parse(data); | |
| }, | |
| getRecentPayouts: async (limit = 10): Promise<RecentPayout[]> => { | |
| const data = await get(`${TRANSPARENCY_ENDPOINT}/payouts`, { params: { limit } }); | |
| return z.array(recentPayoutSchema).parse(data); | |
| }, | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/api/transparency.ts` around lines 27 - 33, The transparencyApi methods
currently return axios data without runtime validation; update getStats and
getRecentPayouts to validate the response with the Zod schemas before returning
(e.g., import your PlatformStatsSchema and RecentPayoutSchema and call
PlatformStatsSchema.parse(...) on the result of get in getStats, and
RecentPayoutSchema.array().parse(...) in getRecentPayouts), or alternately
extend the get helper to accept a validator and run parse inside it; target the
transparencyApi.getStats and transparencyApi.getRecentPayouts calls to ensure
parsed/validated data (use the TRANSPARENCY_ENDPOINT and existing
PlatformStats/RecentPayout types as references).
Benjtalkshow
left a comment
There was a problem hiding this comment.
Hi @Sendi0011 , the workflow is failing.
- Fix all any type usages.
- Clean up unused variables.
- Update useEffect dependency arrays.
- Avoid synchronous setState calls in effects.
- Fix coderabbit error, see sample below:
Summary
Closes #86
Adds a
/transparencypage showing platform-wide funding stats and a recent payouts feed to build trust and accountability.Changes
New Files
lib/api/transparency.tsPlatformStatsandRecentPayouthooks/use-transparency.tsusePlatformStatsanduseRecentPayoutshooks via React Queryapp/transparency/page.tsxapp/api/transparency/stats/route.tsapp/api/transparency/payouts/route.tsModified Files
components/global-navbar.tsxAcceptance Criteria
0, payouts show empty messagesm/lgbreakpointsScreenshots
Desktop
Mobile
Notes
Summary by CodeRabbit