feat(admin): introduce admin dashboard and related configurations - #1279
Conversation
- Added a new admin dashboard application with necessary components and configurations. - Updated environment variables to include ADMIN_PORT and ADMIN_URL. - Enhanced documentation to reflect the addition of the admin interface. - Adjusted API routes to accommodate admin functionalities. ## Test Plan - [ ] Verify that the admin dashboard loads correctly on http://localhost:3006. - [ ] Ensure all new API routes for admin are functional. - [ ] Validate that environment variables are correctly set and utilized.
WalkthroughAdds a new Next.js Admin app served on port 3006 (UI, middleware, auth clients, hooks, components), a new admin-only API route (/admin/metrics), CI/infra/docker updates to build and expose Admin, and removes Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Browser
participant AdminApp as Admin App (Next.js)
participant Middleware
participant Backend as API Backend (/user/me)
participant AdminAPI as API /admin/metrics
participant DB as Database
User->>Browser: Request admin dashboard
Browser->>AdminApp: GET /
AdminApp->>Middleware: middleware(req)
Middleware->>Middleware: read session cookie(s)
alt no session
Middleware->>Browser: redirect /login
else session present
Middleware->>Backend: GET /user/me (with cookie)
alt auth fails or not admin
Backend-->>Middleware: 401/invalid or user.isAdmin=false
Middleware->>Browser: redirect /login or 403
else auth succeeds && isAdmin
Middleware->>AdminApp: allow request
AdminApp->>AdminAPI: GET /admin/metrics
AdminAPI->>DB: run aggregation queries
DB-->>AdminAPI: aggregates
AdminAPI-->>AdminApp: metrics payload
AdminApp->>Browser: render dashboard with metrics
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧠 Learnings (1)📓 Common learnings⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
🔇 Additional comments (3)
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: 17
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
.env.example (1)
29-35: Wire ADMIN_PORT into ORIGIN_URLS and optionally align key ordering.
- Functionally, adding
ADMIN_PORT=3006here is fine, but with a browser-based Admin UI onhttp://localhost:3006hitting the API,ORIGIN_URLSprobably also needs to include that origin (and possibly the other first‑party UIs) to avoid CORS/trusted‑origin surprises. For example:-ORIGIN_URLS=http://localhost:3002 +ORIGIN_URLS=http://localhost:3002,http://localhost:3003,http://localhost:3005,http://localhost:3006
- dotenv-linter suggests
ADMIN_PORTgo beforeAPI_PORT; if you want to keep the env files linter‑clean, you can reorder the SERVICE PORTS block accordingly. This is purely stylistic, not functional.Also applies to: 56-63
.env.unified.example (1)
18-25: Ensure ORIGIN_URLS covers the new ADMIN_URL origin (and tidy key order if desired).
- Adding
ADMIN_URL=http://localhost:3006is good, but with the admin UI talking to the API from that origin,ORIGIN_URLSshould probably be expanded to include it (and other first‑party UIs) to avoid CORS/trusted‑origin issues. For example:-ORIGIN_URLS=http://localhost:3002 +ORIGIN_URLS=http://localhost:3002,http://localhost:3003,http://localhost:3005,http://localhost:3006
- dotenv-linter suggests placing
ADMIN_URLbeforeDOCS_URLfor consistent ordering. That’s cosmetic but keeps env linting clean.
🟡 Minor comments (4)
apps/admin/src/components/ui/carousel.tsx-80-91 (1)
80-91: Keyboard navigation doesn't adapt to vertical orientation.Vertical carousels typically expect
ArrowUp/ArrowDownfor navigation, but this handler only responds toArrowLeft/ArrowRightregardless of orientation.const handleKeyDown = React.useCallback( (event: React.KeyboardEvent<HTMLDivElement>) => { - if (event.key === "ArrowLeft") { + const prevKey = orientation === "horizontal" ? "ArrowLeft" : "ArrowUp"; + const nextKey = orientation === "horizontal" ? "ArrowRight" : "ArrowDown"; + if (event.key === prevKey) { event.preventDefault(); scrollPrev(); - } else if (event.key === "ArrowRight") { + } else if (event.key === nextKey) { event.preventDefault(); scrollNext(); } }, - [scrollPrev, scrollNext], + [orientation, scrollPrev, scrollNext], );apps/admin/README.md-17-17 (1)
17-17: Correct the port number in the README.The README mentions opening
http://localhost:3000, but according to the PR objectives and configuration files, the admin dashboard runs on port 3006.Apply this diff to correct the port:
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +Open [http://localhost:3006](http://localhost:3006) with your browser to see the result.apps/gateway/src/app.ts-77-77 (1)
77-77: Add admin URL to ORIGIN_URLS in docker-compose files.The environment variable defaults in
docker-compose.*.ymlfiles excludehttp://localhost:3006from ORIGIN_URLS, while the code fallback includes it. Update the ORIGIN_URLS defaults to include the admin URL for consistency:ORIGIN_URLS=${ORIGIN_URLS:-http://localhost:3002,http://localhost:3003,http://localhost:3006,http://localhost:4002}This ensures the configuration explicitly matches the intended origins and doesn't rely on hardcoded fallbacks.
apps/admin/src/app/login/page.tsx-160-167 (1)
160-167: Incomplete "Or" separator.The "Or" divider suggests alternative login methods should follow, but nothing is rendered below it. Either add the intended alternative options (e.g., SSO, magic link) or remove this separator.
🧹 Nitpick comments (35)
apps/admin/src/components/ui/command.tsx (1)
23-39:cmdk-input-wrapper+ ESLint disable are acceptable; consider centralizing lint configUsing
cmdk-input-wrapperon the wrapper<div>is required bycmdk, so the inline// eslint-disable-next-line react/no-unknown-propertyis justified. If this pattern appears in multiple places, consider adjusting your ESLint configuration (or rule overrides for this file/directory) instead of repeating inline disables, but this is purely optional.apps/admin/src/components/ui/alert.tsx (1)
22-66: Consider forwarding refs for better component reusability.While the current implementation is functional, using
React.forwardReffor Alert, AlertTitle, and AlertDescription would allow consumers to attach refs to these components when needed.Example for Alert:
-function Alert({ +const Alert = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & VariantProps<typeof alertVariants> +>(function Alert({ className, variant, ...props -}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) { +}, ref) { return ( <div + ref={ref} data-slot="alert" role="alert" className={cn(alertVariants({ variant }), className)} {...props} /> ); -} +}); +Alert.displayName = "Alert";Apply similar patterns to AlertTitle and AlertDescription.
apps/admin/src/components/ui/carousel.tsx (2)
114-127: Consider memoizing the context value.The ESLint disable acknowledges the issue: a new context value object is created every render, which can trigger unnecessary re-renders in consumers. If performance becomes a concern, wrap with
useMemo.+ const contextValue = React.useMemo( + () => ({ + carouselRef, + api, + opts, + orientation: + orientation || (opts?.axis === "y" ? "vertical" : "horizontal"), + scrollPrev, + scrollNext, + canScrollPrev, + canScrollNext, + }), + [carouselRef, api, opts, orientation, scrollPrev, scrollNext, canScrollPrev, canScrollNext], + ); return ( <CarouselContext.Provider - // eslint-disable-next-line react/jsx-no-constructed-context-values - value={{ - carouselRef, - api: api, - opts, - orientation: - orientation || (opts?.axis === "y" ? "vertical" : "horizontal"), - scrollPrev, - scrollNext, - canScrollPrev, - canScrollNext, - }} + value={contextValue} >
241-248: Consider exportinguseCarouselfor custom control scenarios.The
useCarouselhook is defined but not exported. If consumers need to build custom carousel controls (e.g., dot indicators, custom navigation), they'll need access to this hook.export { type CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext, + useCarousel, };apps/docs/content/self-host.mdx (1)
28-30: Document what runs on port 3006 (Admin Dashboard).You expose
-p 3006:3006but the “Accessing Your LLMGateway” section doesn’t mention this port, so it’s unclear to users what it’s for. Consider adding an entry like:After starting either option, you can access: - **Web Interface**: http://localhost:3002 - **Documentation**: http://localhost:3005 - **API Endpoint**: http://localhost:4002 - **Gateway Endpoint**: http://localhost:4001 + - **Admin Dashboard**: http://localhost:3006Also applies to: 74-82
apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md (1)
43-43: Consider formatting the URL as a link.The static analysis tool flagged this as a bare URL. While this is functional, consider formatting it as a markdown link for consistency with documentation best practices.
Apply this diff to format as a link:
-- Admin: http://localhost:3006 +- Admin: [http://localhost:3006](http://localhost:3006)apps/admin/src/hooks/use-mobile.ts (1)
10-18: Consider usingmql.matchesfor consistency.The hook correctly implements mobile detection with proper SSR handling and cleanup. However, the
onChangecallback recheckswindow.innerWidthinstead of using themql.matchesproperty, which is already tracking the same condition.Apply this diff to use the MediaQueryList's matches property:
React.useEffect(() => { const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); const onChange = () => { - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); + setIsMobile(mql.matches); }; mql.addEventListener("change", onChange); setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); return () => mql.removeEventListener("change", onChange); }, []);apps/admin/src/components/admin-shell.tsx (2)
41-50: Consider adding error handling for sign-out failures.If
signOutthrows or fails silently, the user receives no feedback. Consider handling errors to inform the user or retry.const handleSignOut = async () => { - await signOut({ - fetchOptions: { - onSuccess: () => { - queryClient.clear(); - router.push("/login"); + try { + await signOut({ + fetchOptions: { + onSuccess: () => { + queryClient.clear(); + router.push("/login"); + }, }, - }, - }); + }); + } catch { + // Optionally show a toast or error message + console.error("Sign out failed"); + } };
39-39: Minor:pathname === ""is redundant.Next.js
usePathname()returns the pathname starting with/, so the empty string check is unnecessary. This is a minor cleanup.-const isDashboard = pathname === "/" || pathname === ""; +const isDashboard = pathname === "/";apps/admin/src/components/server-data-wrapper.tsx (1)
20-25: Remove unnecessary comment.Per coding guidelines, avoid unnecessary code comments. The code is self-explanatory.
useEffect(() => { - // Set initial data for all queries initialData.forEach(({ queryKey, data }) => { queryClient.setQueryData(queryKey, data); }); }, [queryClient, initialData]);apps/api/src/routes/admin.ts (1)
66-138: Consider parallelizing independent database queries for better performance.These 6 database queries are independent and could be executed concurrently using
Promise.all(), potentially reducing response time significantly.-// Total credits issued (completed credit top-ups, including bonuses) -const [creditsRow] = await db() - .select({...}) - .from(tables.transaction) - .where(...); - -const totalCreditsIssued = Number(creditsRow?.value ?? 0); - -// Total revenue... -const [revenueRow] = await db()... -// ... more sequential queries +const [ + [creditsRow], + [revenueRow], + [usageCostRow], + [signupsRow], + [verifiedRow], + [payingRow], +] = await Promise.all([ + db() + .select({ + value: sql<number>`COALESCE(SUM(CAST(${tables.transaction.creditAmount} AS NUMERIC)), 0)`.as("value"), + }) + .from(tables.transaction) + .where(and(eq(tables.transaction.type, "credit_topup"), eq(tables.transaction.status, "completed"))), + db() + .select({ + value: sql<number>`COALESCE(SUM(CAST(${tables.transaction.amount} AS NUMERIC)), 0)`.as("value"), + }) + .from(tables.transaction) + .where(eq(tables.transaction.status, "completed")), + db() + .select({ + value: sql<number>`COALESCE(SUM(${tables.log.cost}), 0)`.as("value"), + }) + .from(tables.log), + db() + .select({ count: sql<number>`COUNT(*)`.as("count") }) + .from(tables.user), + db() + .select({ count: sql<number>`COUNT(*)`.as("count") }) + .from(tables.user) + .where(eq(tables.user.emailVerified, true)), + db() + .select({ + count: sql<number>`COUNT(DISTINCT ${tables.transaction.organizationId})`.as("count"), + }) + .from(tables.transaction) + .where(eq(tables.transaction.status, "completed")), +]);apps/admin/src/lib/server-api.ts (2)
61-61: Avoidanyfor error type.Consider using
unknownor the specific error type from the OpenAPI client.-let response: { data?: T; error?: any }; +let response: { data?: T; error?: unknown };
89-91: Consider logging more details for debugging.The catch block logs a generic error message without the actual error details. Consider including the error for easier debugging.
-} catch { - console.error(`Server API error for ${method} ${path}`); +} catch (error) { + console.error(`Server API error for ${method} ${path}:`, error); return null; }apps/admin/src/components/ui/sidebar.tsx (1)
531-536: Consider using a local variable instead of mutating the parameter.Mutating the
tooltipparameter triggers the eslint rule and makes the data flow less clear. A local variable avoids the need for the disable comment.- if (typeof tooltip === "string") { - // eslint-disable-next-line no-param-reassign - tooltip = { - children: tooltip, - }; - } + const tooltipProps = + typeof tooltip === "string" ? { children: tooltip } : tooltip; return ( <Tooltip> <TooltipTrigger asChild>{button}</TooltipTrigger> <TooltipContent side="right" align="center" hidden={state !== "collapsed" || isMobile} - {...tooltip} + {...tooltipProps} /> </Tooltip> );apps/admin/package.json (1)
11-11: Consider clarifying the public directory handling in the build script.The build script uses
test -d public && cp -r public ...which will fail the build if thepublicdirectory doesn't exist. Ifpublicis optional, this should use|| trueto prevent build failures:- "build": "pnpm generate && tsc && next build --turbopack && mkdir -p .next/static && cp -r .next/static .next/standalone/apps/admin/.next/ && test -d public && cp -r public .next/standalone/apps/admin/", + "build": "pnpm generate && tsc && next build --turbopack && mkdir -p .next/static && cp -r .next/static .next/standalone/apps/admin/.next/ && (test -d public && cp -r public .next/standalone/apps/admin/ || true)",apps/admin/src/components/ui/skeleton.tsx (1)
1-13: Consider extracting duplicated UI components to a shared package.The Skeleton component is identical to
apps/playground/src/components/ui/skeleton.tsx. If multiple UI components are duplicated across apps, consider creating a shared UI package (e.g.,packages/ui) to apply DRY principles.apps/admin/src/lib/fetch-client.ts (1)
9-21: Consider removing redundant comments.The comments on lines 9 and 21 are self-evident from the function names. Per coding guidelines: "No unnecessary code comments".
-// React hook to get the fetch client export function useFetchClient() {-// React hook to get the API client export function useApi() {apps/admin/src/lib/getUser.ts (2)
12-12: Remove unnecessary code comment.Per the coding guidelines, unnecessary code comments should be avoided. This comment doesn't add value beyond what the code clearly expresses.
- // Get session cookie for authentication const sessionCookie = cookieStore.get(`${key}`);
16-29: Consider handling fetch network errors.The fetch call can throw on network failures (DNS resolution, connection refused, etc.). Currently, any exception will propagate up and may cause unexpected server errors.
- const data = await fetch(`${config.apiBackendUrl}/user/me`, { - method: "GET", - headers: { - Cookie: secureSessionCookie - ? `__Secure-${key}=${secureSessionCookie.value}` - : sessionCookie - ? `${key}=${sessionCookie.value}` - : "", - }, - }); - - if (!data.ok) { - return null; - } + try { + const data = await fetch(`${config.apiBackendUrl}/user/me`, { + method: "GET", + headers: { + Cookie: secureSessionCookie + ? `__Secure-${key}=${secureSessionCookie.value}` + : sessionCookie + ? `${key}=${sessionCookie.value}` + : "", + }, + }); + + if (!data.ok) { + return null; + } + + const user: User = await data.json(); + return user; + } catch { + return null; + }apps/admin/src/lib/auth-client.ts (2)
6-6: Remove unnecessary code comments.The comments at lines 6 and 17 are redundant as the function names are already self-explanatory.
As per coding guidelines, avoid unnecessary code comments.
Apply this diff to remove the comments:
-// React hook to get the auth client export function useAuthClient() {-// React hook for auth methods export function useAuth() {Also applies to: 17-17
10-14: Normalize apiUrl to prevent double slashes.The baseURL concatenates
config.apiUrl + "/auth", which could result in a double slash ifconfig.apiUrlends with/. Add URL normalization to remove trailing slashes fromconfig.apiUrlbefore concatenation, or use a utility likenew URL()to safely construct the endpoint URL.apps/admin/next.config.ts (1)
13-14: Remove commented-out code.The commented-out turbopack configuration options should either be enabled or removed to keep the codebase clean.
As per coding guidelines, avoid keeping commented-out code.
Apply this diff:
experimental: { - // turbopackFileSystemCacheForDev: true, - // turbopackFileSystemCacheForBuild: true, },apps/admin/src/app/page.tsx (2)
42-51: Missing default styling whenaccentis undefined.When no
accentprop is provided, the icon container only receives base styles without any color theme. Consider adding a default/neutral accent style.className={cn( "inline-flex h-9 w-9 items-center justify-center rounded-full border text-xs", + !accent && + "border-border bg-muted/50 text-muted-foreground", accent === "green" && "border-emerald-500/30 bg-emerald-500/10 text-emerald-400",
16-59: Consider extractingMetricCardto a separate file.The
MetricCardcomponent is self-contained and could be reused across other admin pages. Extracting it to@/components/metric-card.tsxwould improve modularity.apps/admin/src/lib/utils.ts (1)
8-54: Consider usingunknowninstead ofanyfor better type safety.The error handling logic correctly handles multiple error formats including Zod-OpenAPI structures. However, using
unknowninstead ofanywould provide better type safety while still accepting any error type.Apply this diff to improve type safety:
-export function getErrorMessage(error: any): string { +export function getErrorMessage(error: unknown): string {As per coding guidelines, avoid
anyunless absolutely necessary. Theunknowntype serves the same purpose here while enforcing type checks within the function body.apps/admin/middleware.ts (2)
17-25: Path exclusion logic is duplicated between middleware body and matcher config.The
ifblock (lines 17-25) checks paths like/login,/signup,/_next, while the matcher (line 80) excludes_next/static,_next/image,favicon.ico. These serve different purposes but the overlap with/_nextand/faviconis confusing.The matcher controls which requests invoke the middleware; the
ifblock provides early-exit logic. Consider consolidating or documenting the distinction.Also applies to: 79-81
70-74: Catch block swallows error context.Returning a generic 403 for all errors (network failures, JSON parse errors, etc.) makes debugging difficult. Consider logging the error server-side or distinguishing between auth failures and infrastructure errors.
- } catch { + } catch (err) { + console.error("Admin middleware error:", err); return new NextResponse("Forbidden: admin access required", { status: 403, }); }apps/admin/src/hooks/useUser.ts (2)
39-53: FirstuseEffecthas no side effects - appears to be dead/scaffold code.This effect only contains early-return conditions but performs no action. If this is placeholder for future onboarding logic, consider adding a TODO comment or removing it to reduce confusion.
- // Check for onboarding completion for all authenticated users - useEffect(() => { - if (!data?.user || isLoading) { - return; - } - - const currentPath = pathname; - const isAuthPage = ["/login", "/signup"].includes(currentPath); - const isLandingPage = currentPath === "/"; - - // Don't redirect if already on auth pages - if (isAuthPage || isLandingPage) { - return; - } - }, [data?.user, isLoading, router, pathname]);
73-81: Redundant entries in dependency array.The array includes both the
optionsobject and its destructured properties (options?.redirectTo,options?.redirectWhen). Sinceoptionsalready captures reference changes, the individual property checks are redundant and can trigger unnecessary re-renders ifoptionsis recreated.}, [ data?.user, isLoading, error, router, - options?.redirectTo, - options?.redirectWhen, options, ]);apps/admin/src/components/ui/input-group.tsx (1)
131-145: Consider usingforwardReffor consistency withInputGroupTextarea.
InputGroupTextareausesforwardRef(lines 147-163), butInputGroupInputdoes not. For consistency and to allow parent components to access the underlying input element directly, consider wrappingInputGroupInputwithforwardRefas well.-function InputGroupInput({ - className, - ...props -}: React.ComponentProps<"input">) { - return ( +const InputGroupInput = React.forwardRef< + HTMLInputElement, + React.ComponentProps<"input"> +>(({ className, ...props }, ref) => { + return ( <Input + ref={ref} data-slot="input-group-control" className={cn( "flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent", className, )} {...props} /> ); -} +}); +InputGroupInput.displayName = "InputGroupInput";apps/admin/src/components/ui/dropdown-menu.tsx (1)
34-52:DropdownMenuContentincludes Portal internally—document to avoid double-portal.
DropdownMenuContentalready wraps content inDropdownMenuPrimitive.Portal(line 40), whileDropdownMenuPortalis also exported separately. This is fine for flexibility, but consumers should be aware that using both together would create nested portals. Consider adding a brief JSDoc comment.apps/admin/src/components/ui/dialog.tsx (1)
57-59: Minor: Redundantdata-slotattribute.
DialogPortal(line 24) already setsdata-slot="dialog-portal", so passing it again on line 58 is redundant. The second one will override the first, so no functional issue.- <DialogPortal data-slot="dialog-portal"> + <DialogPortal>apps/admin/src/components/ui/sheet.tsx (2)
75-78: Missingdata-slotattribute on close button.The close button inside
SheetContentdoesn't have adata-slot="sheet-close"attribute, unlike the exportedSheetClosecomponent (line 22). This inconsistency could affect targeting for testing or styling.- <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none"> + <SheetPrimitive.Close data-slot="sheet-close" className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
47-82: Consider addingshowCloseButtonprop for consistency withDialog.
DialogContenthas ashowCloseButtonprop to conditionally render the close button, butSheetContentalways renders it. For API consistency across modal-like components, consider adding the same prop here.apps/admin/src/lib/stripe.ts (1)
25-35: Guard against state updates after unmount and normalize the error typeThe current effect is fine functionally, but if the component unmounts before
getStripePromise()resolves, React can warn about setting state on an unmounted component. Also,errmay not always be anErrorinstance.You can make the hook more robust with a simple mounted flag and error normalization:
- useEffect(() => { - getStripePromise() - .then((stripeInstance) => { - setStripe(stripeInstance); - setIsLoading(false); - }) - .catch((err) => { - setError(err); - setIsLoading(false); - }); - }, []); + useEffect(() => { + let isMounted = true; + + getStripePromise() + .then((stripeInstance) => { + if (!isMounted) return; + setStripe(stripeInstance); + setIsLoading(false); + }) + .catch((err) => { + if (!isMounted) return; + const normalizedError = + err instanceof Error ? err : new Error("Failed to load Stripe"); + setError(normalizedError); + setIsLoading(false); + }); + + return () => { + isMounted = false; + }; + }, []);This keeps the existing API (
{ stripe, isLoading, error }) but avoids potential React warnings and ensureserroris always anError.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (9)
apps/admin/public/favicon/android-chrome-192x192.pngis excluded by!**/*.pngapps/admin/public/favicon/android-chrome-512x512.pngis excluded by!**/*.pngapps/admin/public/favicon/apple-touch-icon.pngis excluded by!**/*.pngapps/admin/public/favicon/favicon-16x16.pngis excluded by!**/*.pngapps/admin/public/favicon/favicon-32x32.pngis excluded by!**/*.pngapps/admin/public/favicon/favicon.icois excluded by!**/*.icoapps/admin/public/opengraph.pngis excluded by!**/*.pngapps/admin/src/lib/api/v1.d.tsis excluded by!**/v1.d.tspnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (91)
.dockerignore(0 hunks).env.example(1 hunks).env.unified.example(1 hunks).github/start.sh(2 hunks).github/test-split-docker.sh(1 hunks).github/test-unified-docker.sh(1 hunks)AGENTS.md(2 hunks)CLAUDE.md(3 hunks)README.md(1 hunks)apps/admin/.gitignore(1 hunks)apps/admin/.lintstagedrc.json(1 hunks)apps/admin/.prettierignore(1 hunks)apps/admin/README.md(1 hunks)apps/admin/components.json(1 hunks)apps/admin/eslint.config.mjs(1 hunks)apps/admin/middleware.ts(1 hunks)apps/admin/next.config.ts(1 hunks)apps/admin/package.json(1 hunks)apps/admin/postcss.config.mjs(1 hunks)apps/admin/public/favicon/site.webmanifest(1 hunks)apps/admin/src/app/globals.css(1 hunks)apps/admin/src/app/layout.tsx(1 hunks)apps/admin/src/app/login/page.tsx(1 hunks)apps/admin/src/app/page.tsx(1 hunks)apps/admin/src/components/admin-shell.tsx(1 hunks)apps/admin/src/components/auth/user-provider.tsx(1 hunks)apps/admin/src/components/landing/theme-toggle.tsx(1 hunks)apps/admin/src/components/server-data-wrapper.tsx(1 hunks)apps/admin/src/components/ui/alert.tsx(1 hunks)apps/admin/src/components/ui/avatar.tsx(1 hunks)apps/admin/src/components/ui/badge.tsx(1 hunks)apps/admin/src/components/ui/button.tsx(1 hunks)apps/admin/src/components/ui/carousel.tsx(1 hunks)apps/admin/src/components/ui/checkbox.tsx(1 hunks)apps/admin/src/components/ui/collapsible.tsx(1 hunks)apps/admin/src/components/ui/command.tsx(1 hunks)apps/admin/src/components/ui/dialog.tsx(1 hunks)apps/admin/src/components/ui/dropdown-menu.tsx(1 hunks)apps/admin/src/components/ui/form.tsx(1 hunks)apps/admin/src/components/ui/hover-card.tsx(1 hunks)apps/admin/src/components/ui/input-group.tsx(1 hunks)apps/admin/src/components/ui/input.tsx(1 hunks)apps/admin/src/components/ui/label.tsx(1 hunks)apps/admin/src/components/ui/logo.tsx(1 hunks)apps/admin/src/components/ui/popover.tsx(1 hunks)apps/admin/src/components/ui/progress.tsx(1 hunks)apps/admin/src/components/ui/scroll-area.tsx(1 hunks)apps/admin/src/components/ui/select.tsx(1 hunks)apps/admin/src/components/ui/separator.tsx(1 hunks)apps/admin/src/components/ui/sheet.tsx(1 hunks)apps/admin/src/components/ui/sidebar.tsx(1 hunks)apps/admin/src/components/ui/skeleton.tsx(1 hunks)apps/admin/src/components/ui/sonner.tsx(1 hunks)apps/admin/src/components/ui/tabs.tsx(1 hunks)apps/admin/src/components/ui/textarea.tsx(1 hunks)apps/admin/src/components/ui/tooltip.tsx(1 hunks)apps/admin/src/hooks/use-mobile.ts(1 hunks)apps/admin/src/hooks/useUser.ts(1 hunks)apps/admin/src/lib/admin-metrics.ts(1 hunks)apps/admin/src/lib/auth-client.ts(1 hunks)apps/admin/src/lib/config-server.ts(1 hunks)apps/admin/src/lib/config.tsx(1 hunks)apps/admin/src/lib/fetch-client.ts(1 hunks)apps/admin/src/lib/getUser.ts(1 hunks)apps/admin/src/lib/providers.tsx(1 hunks)apps/admin/src/lib/server-api.ts(1 hunks)apps/admin/src/lib/stripe.ts(1 hunks)apps/admin/src/lib/types.ts(1 hunks)apps/admin/src/lib/utils.ts(1 hunks)apps/admin/src/types/next-themes.d.ts(1 hunks)apps/admin/tsconfig.json(1 hunks)apps/api/src/auth/config.ts(1 hunks)apps/api/src/index.ts(1 hunks)apps/api/src/routes/admin.ts(1 hunks)apps/api/src/routes/index.ts(2 hunks)apps/api/src/routes/user.ts(4 hunks)apps/docs/app/api/proxy/route.ts(1 hunks)apps/docs/content/self-host.mdx(1 hunks)apps/gateway/src/app.ts(1 hunks)apps/playground/src/lib/config-server.ts(2 hunks)apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md(2 hunks)apps/ui/src/lib/config-server.ts(2 hunks)infra/bunnyshell.yaml(1 hunks)infra/docker-compose.split.local.yml(3 hunks)infra/docker-compose.split.yml(3 hunks)infra/docker-compose.unified.local.yml(2 hunks)infra/docker-compose.unified.yml(2 hunks)infra/supervisord.conf(1 hunks)infra/unified.dockerfile(1 hunks)package.json(1 hunks)turbo.json(0 hunks)
💤 Files with no reviewable changes (2)
- .dockerignore
- turbo.json
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Always use top-levelimport, never use require or dynamic imports
No unnecessary code comments
Files:
apps/admin/next.config.tsapps/gateway/src/app.tsapps/ui/src/lib/config-server.tsapps/admin/src/lib/types.tsapps/api/src/auth/config.tsapps/admin/src/components/ui/separator.tsxapps/admin/src/components/ui/checkbox.tsxapps/admin/src/types/next-themes.d.tsapps/admin/src/lib/stripe.tsapps/admin/src/components/ui/progress.tsxapps/api/src/index.tsapps/admin/src/components/ui/logo.tsxapps/admin/src/app/layout.tsxapps/admin/src/components/auth/user-provider.tsxapps/admin/src/components/server-data-wrapper.tsxapps/admin/src/components/ui/button.tsxapps/admin/src/hooks/use-mobile.tsapps/admin/src/app/page.tsxapps/admin/src/components/ui/collapsible.tsxapps/admin/src/lib/providers.tsxapps/admin/src/components/landing/theme-toggle.tsxapps/admin/src/lib/fetch-client.tsapps/admin/src/components/ui/alert.tsxapps/admin/src/components/ui/avatar.tsxapps/api/src/routes/admin.tsapps/api/src/routes/user.tsapps/admin/src/components/admin-shell.tsxapps/admin/src/lib/admin-metrics.tsapps/admin/src/lib/utils.tsapps/admin/src/components/ui/input-group.tsxapps/admin/src/lib/server-api.tsapps/api/src/routes/index.tsapps/admin/src/components/ui/scroll-area.tsxapps/admin/src/components/ui/tabs.tsxapps/admin/src/components/ui/tooltip.tsxapps/admin/src/components/ui/badge.tsxapps/admin/src/lib/auth-client.tsapps/admin/src/components/ui/hover-card.tsxapps/admin/src/app/login/page.tsxapps/admin/src/lib/config-server.tsapps/admin/src/components/ui/skeleton.tsxapps/admin/src/hooks/useUser.tsapps/admin/src/components/ui/dropdown-menu.tsxapps/docs/app/api/proxy/route.tsapps/admin/src/lib/config.tsxapps/admin/src/components/ui/carousel.tsxapps/admin/src/lib/getUser.tsapps/admin/src/components/ui/command.tsxapps/admin/src/components/ui/sheet.tsxapps/admin/src/components/ui/textarea.tsxapps/admin/src/components/ui/select.tsxapps/admin/src/components/ui/popover.tsxapps/playground/src/lib/config-server.tsapps/admin/middleware.tsapps/admin/src/components/ui/sidebar.tsxapps/admin/src/components/ui/form.tsxapps/admin/src/components/ui/sonner.tsxapps/admin/src/components/ui/dialog.tsxapps/admin/src/components/ui/label.tsxapps/admin/src/components/ui/input.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use cookies for user-settings which are not saved in the database to ensure SSR works
**/*.{ts,tsx,js,jsx}: Always use tabs for indentation
Always use top-levelimport, never use require or dynamic imports
Avoid unnecessary code comments
Files:
apps/admin/next.config.tsapps/gateway/src/app.tsapps/ui/src/lib/config-server.tsapps/admin/src/lib/types.tsapps/api/src/auth/config.tsapps/admin/src/components/ui/separator.tsxapps/admin/src/components/ui/checkbox.tsxapps/admin/src/types/next-themes.d.tsapps/admin/src/lib/stripe.tsapps/admin/src/components/ui/progress.tsxapps/api/src/index.tsapps/admin/src/components/ui/logo.tsxapps/admin/src/app/layout.tsxapps/admin/src/components/auth/user-provider.tsxapps/admin/src/components/server-data-wrapper.tsxapps/admin/src/components/ui/button.tsxapps/admin/src/hooks/use-mobile.tsapps/admin/src/app/page.tsxapps/admin/src/components/ui/collapsible.tsxapps/admin/src/lib/providers.tsxapps/admin/src/components/landing/theme-toggle.tsxapps/admin/src/lib/fetch-client.tsapps/admin/src/components/ui/alert.tsxapps/admin/src/components/ui/avatar.tsxapps/api/src/routes/admin.tsapps/api/src/routes/user.tsapps/admin/src/components/admin-shell.tsxapps/admin/src/lib/admin-metrics.tsapps/admin/src/lib/utils.tsapps/admin/src/components/ui/input-group.tsxapps/admin/src/lib/server-api.tsapps/api/src/routes/index.tsapps/admin/src/components/ui/scroll-area.tsxapps/admin/src/components/ui/tabs.tsxapps/admin/src/components/ui/tooltip.tsxapps/admin/src/components/ui/badge.tsxapps/admin/src/lib/auth-client.tsapps/admin/src/components/ui/hover-card.tsxapps/admin/src/app/login/page.tsxapps/admin/src/lib/config-server.tsapps/admin/src/components/ui/skeleton.tsxapps/admin/src/hooks/useUser.tsapps/admin/src/components/ui/dropdown-menu.tsxapps/docs/app/api/proxy/route.tsapps/admin/src/lib/config.tsxapps/admin/src/components/ui/carousel.tsxapps/admin/src/lib/getUser.tsapps/admin/src/components/ui/command.tsxapps/admin/src/components/ui/sheet.tsxapps/admin/src/components/ui/textarea.tsxapps/admin/src/components/ui/select.tsxapps/admin/src/components/ui/popover.tsxapps/playground/src/lib/config-server.tsapps/admin/middleware.tsapps/admin/src/components/ui/sidebar.tsxapps/admin/src/components/ui/form.tsxapps/admin/src/components/ui/sonner.tsxapps/admin/src/components/ui/dialog.tsxapps/admin/src/components/ui/label.tsxapps/admin/src/components/ui/input.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use Drizzle ORM with latest object syntax for database operations
For database reads: Usedb().query.<table>.findMany()ordb().query.<table>.findFirst()Never use
anyoras anyin TypeScript code unless absolutely necessary
Files:
apps/admin/next.config.tsapps/gateway/src/app.tsapps/ui/src/lib/config-server.tsapps/admin/src/lib/types.tsapps/api/src/auth/config.tsapps/admin/src/components/ui/separator.tsxapps/admin/src/components/ui/checkbox.tsxapps/admin/src/types/next-themes.d.tsapps/admin/src/lib/stripe.tsapps/admin/src/components/ui/progress.tsxapps/api/src/index.tsapps/admin/src/components/ui/logo.tsxapps/admin/src/app/layout.tsxapps/admin/src/components/auth/user-provider.tsxapps/admin/src/components/server-data-wrapper.tsxapps/admin/src/components/ui/button.tsxapps/admin/src/hooks/use-mobile.tsapps/admin/src/app/page.tsxapps/admin/src/components/ui/collapsible.tsxapps/admin/src/lib/providers.tsxapps/admin/src/components/landing/theme-toggle.tsxapps/admin/src/lib/fetch-client.tsapps/admin/src/components/ui/alert.tsxapps/admin/src/components/ui/avatar.tsxapps/api/src/routes/admin.tsapps/api/src/routes/user.tsapps/admin/src/components/admin-shell.tsxapps/admin/src/lib/admin-metrics.tsapps/admin/src/lib/utils.tsapps/admin/src/components/ui/input-group.tsxapps/admin/src/lib/server-api.tsapps/api/src/routes/index.tsapps/admin/src/components/ui/scroll-area.tsxapps/admin/src/components/ui/tabs.tsxapps/admin/src/components/ui/tooltip.tsxapps/admin/src/components/ui/badge.tsxapps/admin/src/lib/auth-client.tsapps/admin/src/components/ui/hover-card.tsxapps/admin/src/app/login/page.tsxapps/admin/src/lib/config-server.tsapps/admin/src/components/ui/skeleton.tsxapps/admin/src/hooks/useUser.tsapps/admin/src/components/ui/dropdown-menu.tsxapps/docs/app/api/proxy/route.tsapps/admin/src/lib/config.tsxapps/admin/src/components/ui/carousel.tsxapps/admin/src/lib/getUser.tsapps/admin/src/components/ui/command.tsxapps/admin/src/components/ui/sheet.tsxapps/admin/src/components/ui/textarea.tsxapps/admin/src/components/ui/select.tsxapps/admin/src/components/ui/popover.tsxapps/playground/src/lib/config-server.tsapps/admin/middleware.tsapps/admin/src/components/ui/sidebar.tsxapps/admin/src/components/ui/form.tsxapps/admin/src/components/ui/sonner.tsxapps/admin/src/components/ui/dialog.tsxapps/admin/src/components/ui/label.tsxapps/admin/src/components/ui/input.tsx
**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
For database reads, use
db().query.<table>.findMany()ordb().query.<table>.findFirst()with Drizzle ORM
Files:
apps/admin/next.config.tsapps/gateway/src/app.tsapps/ui/src/lib/config-server.tsapps/admin/src/lib/types.tsapps/api/src/auth/config.tsapps/admin/src/types/next-themes.d.tsapps/admin/src/lib/stripe.tsapps/api/src/index.tsapps/admin/src/hooks/use-mobile.tsapps/admin/src/lib/fetch-client.tsapps/api/src/routes/admin.tsapps/api/src/routes/user.tsapps/admin/src/lib/admin-metrics.tsapps/admin/src/lib/utils.tsapps/admin/src/lib/server-api.tsapps/api/src/routes/index.tsapps/admin/src/lib/auth-client.tsapps/admin/src/lib/config-server.tsapps/admin/src/hooks/useUser.tsapps/docs/app/api/proxy/route.tsapps/admin/src/lib/getUser.tsapps/playground/src/lib/config-server.tsapps/admin/middleware.ts
apps/{gateway,api}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Zod schemas for validation in Hono applications
Files:
apps/gateway/src/app.tsapps/api/src/auth/config.tsapps/api/src/index.tsapps/api/src/routes/admin.tsapps/api/src/routes/user.tsapps/api/src/routes/index.ts
apps/{gateway,api}/src/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{gateway,api}/src/**/*.ts: Runpnpm buildif API routes were modified
Use Zod schemas for validation in Hono applications
Files:
apps/gateway/src/app.tsapps/api/src/auth/config.tsapps/api/src/index.tsapps/api/src/routes/admin.tsapps/api/src/routes/user.tsapps/api/src/routes/index.ts
apps/ui/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Next.js App Router with React Server Components for frontend development
Files:
apps/ui/src/lib/config-server.ts
apps/{ui,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use
next/linkfor links andnext/navigation's router for programmatic navigation
Files:
apps/ui/src/lib/config-server.tsapps/playground/src/lib/config-server.ts
apps/{ui,playground}/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
next/linkfor links andnext/navigationrouter for programmatic navigation in Next.js applications
Files:
apps/ui/src/lib/config-server.tsapps/playground/src/lib/config-server.ts
apps/{ui,playground,api}/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use cookies for user-settings that are not saved in the database to ensure SSR works
Files:
apps/ui/src/lib/config-server.tsapps/api/src/auth/config.tsapps/api/src/index.tsapps/api/src/routes/admin.tsapps/api/src/routes/user.tsapps/api/src/routes/index.tsapps/playground/src/lib/config-server.ts
🧠 Learnings (18)
📚 Learning: 2025-11-24T20:02:21.810Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T20:02:21.810Z
Learning: Applies to apps/ui/**/*.{ts,tsx} : Use Next.js App Router with React Server Components for frontend development
Applied to files:
apps/admin/next.config.tsapps/admin/src/components/ui/separator.tsxapps/admin/src/components/ui/checkbox.tsxapps/admin/src/components/ui/progress.tsxapps/admin/src/app/layout.tsxapps/admin/src/components/server-data-wrapper.tsxapps/admin/src/components/ui/collapsible.tsxapps/admin/src/components/admin-shell.tsxapps/admin/src/components/ui/scroll-area.tsxapps/admin/src/components/ui/tabs.tsxapps/admin/README.mdapps/admin/src/app/login/page.tsxapps/admin/src/components/ui/skeleton.tsxapps/admin/src/hooks/useUser.tsapps/admin/src/components/ui/dropdown-menu.tsxapps/admin/src/lib/config.tsxapps/admin/src/components/ui/carousel.tsxapps/admin/src/components/ui/select.tsxapps/admin/middleware.tsapps/admin/src/components/ui/sidebar.tsx
📚 Learning: 2025-11-28T15:24:54.183Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-28T15:24:54.183Z
Learning: Applies to apps/{ui,playground}/src/**/*.{ts,tsx} : Use `next/link` for links and `next/navigation` router for programmatic navigation in Next.js applications
Applied to files:
apps/admin/next.config.tsapps/admin/src/app/layout.tsxapps/admin/src/components/ui/tabs.tsxapps/admin/README.mdapps/admin/src/components/ui/carousel.tsxapps/admin/middleware.ts
📚 Learning: 2025-11-24T20:02:21.810Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T20:02:21.810Z
Learning: Applies to apps/{ui,playground}/**/*.{ts,tsx} : Use `next/link` for links and `next/navigation`'s router for programmatic navigation
Applied to files:
apps/admin/next.config.tsapps/admin/src/app/layout.tsxapps/admin/src/components/ui/tabs.tsxapps/admin/src/components/ui/carousel.tsxapps/admin/middleware.ts
📚 Learning: 2025-11-28T15:24:54.183Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-28T15:24:54.183Z
Learning: Applies to apps/{gateway,api}/src/**/*.ts : Run `pnpm build` if API routes were modified
Applied to files:
apps/gateway/src/app.tsAGENTS.mdapps/admin/.lintstagedrc.jsonCLAUDE.mdapps/api/src/routes/index.ts
📚 Learning: 2025-11-24T20:02:21.811Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T20:02:21.811Z
Learning: Run `pnpm build` to ensure production builds work
Applied to files:
AGENTS.mdapps/admin/.lintstagedrc.jsonCLAUDE.md
📚 Learning: 2025-11-28T15:24:54.184Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-28T15:24:54.184Z
Learning: Run `pnpm build` after finishing work on a feature to ensure production builds work
Applied to files:
AGENTS.mdCLAUDE.md
📚 Learning: 2025-11-24T20:02:21.810Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T20:02:21.810Z
Learning: Always use pnpm for package management
Applied to files:
AGENTS.mdapps/admin/.lintstagedrc.jsonCLAUDE.md
📚 Learning: 2025-11-24T20:02:21.811Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T20:02:21.811Z
Learning: Run `pnpm format` after code changes
Applied to files:
AGENTS.mdapps/admin/.lintstagedrc.jsonCLAUDE.md
📚 Learning: 2025-11-28T15:24:54.184Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-28T15:24:54.184Z
Learning: Always run `pnpm format` before committing code to ensure consistent formatting and linting
Applied to files:
AGENTS.mdapps/admin/.lintstagedrc.jsonCLAUDE.md
📚 Learning: 2025-11-24T20:02:21.811Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T20:02:21.811Z
Learning: Run `pnpm test:unit` and `pnpm test:e2e` after adding features
Applied to files:
AGENTS.mdCLAUDE.md
📚 Learning: 2025-11-24T20:02:21.811Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T20:02:21.811Z
Learning: For schema changes: Use `pnpm run setup` instead of writing migrations which will generate .sql files, and always sync schema with `pnpm run setup` after table/column changes
Applied to files:
AGENTS.mdCLAUDE.md
📚 Learning: 2025-11-28T15:24:54.183Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-28T15:24:54.183Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Always use tabs for indentation
Applied to files:
apps/admin/.lintstagedrc.json
📚 Learning: 2025-11-24T20:02:21.810Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T20:02:21.810Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : No unnecessary code comments
Applied to files:
apps/admin/.lintstagedrc.json
📚 Learning: 2025-09-22T18:30:32.055Z
Learnt from: smakosh
Repo: theopenco/llmgateway PR: 911
File: apps/ui/src/lib/components/tweet-card.tsx:170-174
Timestamp: 2025-09-22T18:30:32.055Z
Learning: In the tweet-card component at apps/ui/src/lib/components/tweet-card.tsx, the use of dangerouslySetInnerHTML for rendering tweet entity text is acceptable. The react-tweet library content is considered safe to render as HTML in this context.
Applied to files:
apps/admin/src/components/ui/hover-card.tsx
📚 Learning: 2025-09-22T18:29:26.406Z
Learnt from: smakosh
Repo: theopenco/llmgateway PR: 911
File: apps/ui/src/lib/components/tweet-card.tsx:244-255
Timestamp: 2025-09-22T18:29:26.406Z
Learning: In the tweet-card component at apps/ui/src/lib/components/tweet-card.tsx, the TweetMedia component is intentionally not used in the MagicTweet component. This is a deliberate design decision to keep testimonials text-focused without rendering images or videos from tweets.
Applied to files:
apps/admin/src/components/ui/hover-card.tsx
📚 Learning: 2025-11-28T15:24:54.183Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-28T15:24:54.183Z
Learning: Applies to apps/{ui,playground,api}/src/**/*.{ts,tsx} : Use cookies for user-settings that are not saved in the database to ensure SSR works
Applied to files:
apps/admin/src/lib/getUser.tsapps/admin/middleware.ts
📚 Learning: 2025-11-24T20:02:21.810Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T20:02:21.810Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use cookies for user-settings which are not saved in the database to ensure SSR works
Applied to files:
apps/admin/src/lib/getUser.tsapps/admin/middleware.ts
📚 Learning: 2025-11-28T15:24:54.183Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-28T15:24:54.183Z
Learning: Applies to packages/**/*.ts : Use DRY principles for code reuse across the monorepo with shared packages
Applied to files:
apps/admin/tsconfig.json
🧬 Code graph analysis (8)
apps/admin/src/lib/stripe.ts (1)
apps/api/src/routes/payments.ts (1)
stripe(13-18)
apps/admin/src/app/layout.tsx (1)
apps/admin/src/components/admin-shell.tsx (1)
AdminShell(33-116)
apps/admin/src/components/auth/user-provider.tsx (1)
apps/playground/src/components/auth/user-provider.tsx (1)
UserProvider(15-27)
apps/admin/src/app/page.tsx (1)
apps/admin/src/lib/admin-metrics.ts (1)
getAdminDashboardMetrics(14-25)
apps/api/src/routes/user.ts (1)
packages/db/src/schema.ts (1)
user(41-53)
apps/api/src/routes/index.ts (1)
apps/api/src/routes/admin.ts (1)
admin(9-9)
apps/admin/src/components/ui/skeleton.tsx (1)
apps/playground/src/components/ui/skeleton.tsx (1)
Skeleton(3-11)
apps/admin/middleware.ts (1)
packages/db/src/schema.ts (1)
session(55-73)
🪛 dotenv-linter (4.0.0)
.env.unified.example
[warning] 21-21: [UnorderedKey] The ADMIN_URL key should go before the DOCS_URL key
(UnorderedKey)
.env.example
[warning] 35-35: [UnorderedKey] The ADMIN_PORT key should go before the API_PORT key
(UnorderedKey)
🪛 GitHub Actions: ci
apps/admin/package.json
[error] 1-1: Lockfile mismatch detected: package.json specifiers do not match the lockfile. Run 'pnpm install' or 'pnpm install --no-frozen-lockfile' to update the lockfile.
package.json
[error] 1-1: Lockfile mismatch detected: package.json specifiers do not match the lockfile. Run 'pnpm install' or 'pnpm install --no-frozen-lockfile' to update the lockfile.
🪛 GitHub Actions: e2e
apps/admin/package.json
[error] 1-1: pnpm install failed with --frozen-lockfile: pnpm-lock.yaml is not up to date with apps/admin/package.json. Run 'pnpm install' to update the lockfile. 1 dependencies were removed: @llmgateway/db@workspace:*
package.json
[error] 1-1: pnpm install failed with --frozen-lockfile: pnpm-lock.yaml is not up to date with apps/admin/package.json. Run 'pnpm install' to update the lockfile. 1 dependencies were removed: @llmgateway/db@workspace:*
🪛 markdownlint-cli2 (0.18.1)
AGENTS.md
138-138: Bare URL used
(MD034, no-bare-urls)
apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md
43-43: Bare URL used
(MD034, no-bare-urls)
CLAUDE.md
148-148: Bare URL used
(MD034, no-bare-urls)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: autofix
| export default function Login() { | ||
| const queryClient = useQueryClient(); | ||
| const router = useRouter(); | ||
| const searchParams = useSearchParams(); | ||
| const [isLoading, setIsLoading] = useState(false); | ||
| const { signIn } = useAuth(); | ||
| const returnUrl = getSafeRedirectUrl(searchParams.get("returnUrl")); | ||
|
|
||
| useUser({ | ||
| redirectTo: returnUrl, | ||
| redirectWhen: "authenticated", | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
head -50 apps/admin/src/app/login/page.tsxRepository: theopenco/llmgateway
Length of output: 1407
🌐 Web query:
Next.js App Router useSearchParams Suspense boundary requirement 2025
💡 Result:
Short answer: As of 2025 you must wrap any Client Component that calls useSearchParams() in a React Suspense boundary when the route is statically prerendered — otherwise the build will fail with the "Missing Suspense boundary with useSearchParams" error. If you don't want that behavior you can make the route dynamic (e.g., use connection in a Server Component) or pass the page's searchParams prop into the Client Component instead. [1][2][3]
Key sources:
- Next.js useSearchParams docs (behavior + recommendation to wrap in ). [1]
- Next.js error doc "Missing Suspense boundary with useSearchParams" (explains build failure and fixes). [2]
- Active Next.js issue/discussion showing the requirement and user reports in 2024–2025. [3]
If you want, I can show a minimal example (Suspense wrapper, or passing searchParams from the Page) or help update your code to fix the build error.
Wrap useSearchParams with a Suspense boundary to prevent build failure.
In Next.js App Router, Client Components that call useSearchParams() must be wrapped in a Suspense boundary when the route is statically prerendered. Without it, the build will fail with "Missing Suspense boundary with useSearchParams" error. Either wrap the component using <Suspense>, make the route dynamic, or pass searchParams from the parent Server Component.
🤖 Prompt for AI Agents
In apps/admin/src/app/login/page.tsx around lines 42 to 53, the call to
useSearchParams() in this Client Component will break static prerendering; wrap
the component (or the part that calls useSearchParams) in a React.Suspense
boundary so Next.js can handle the client-side params, or alternatively obtain
searchParams in a parent Server Component and pass them down (or make the route
dynamic). Concretely: move useSearchParams usage into a child component wrapped
with <Suspense fallback={...}> (or pass searchParams from the server) and keep
the rest of Login as-is to avoid the "Missing Suspense boundary with
useSearchParams" build error.
| export async function fetchServerData<T>( | ||
| method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", | ||
| path: keyof paths, | ||
| options?: any, | ||
| ): Promise<T | null> { |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Avoid any type for options parameter.
As per coding guidelines, any should not be used. Consider using the proper type from openapi-fetch or a more specific type.
+import type { FetchOptions } from "openapi-fetch";
+
export async function fetchServerData<T>(
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
path: keyof paths,
- options?: any,
+ options?: FetchOptions<unknown>,
): Promise<T | null> {If FetchOptions doesn't fit your needs, consider at minimum using unknown or defining a specific interface for the expected options shape.
🤖 Prompt for AI Agents
In apps/admin/src/lib/server-api.ts around lines 53 to 57, the options parameter
is typed as any which violates the guideline; replace any with a concrete type
(preferably the FetchOptions type from openapi-fetch) by importing that type and
updating the function signature to options?: FetchOptions; if FetchOptions is
unsuitable, type options as unknown or define a small interface describing the
exact fields used (e.g., body, headers, signal) and update all call sites to
pass the correctly shaped object or to cast where necessary; ensure imports and
any downstream usages are adjusted to the new type so the code compiles without
using any.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
.github/workflows/images.yml(3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T20:02:21.822Z
Learning: Applies to apps/ui/**/*.{ts,tsx} : Use Next.js App Router with React Server Components for frontend development
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
- GitHub Check: e2e-shards (1)
- GitHub Check: e2e-shards (5)
- GitHub Check: e2e-shards (4)
- GitHub Check: e2e-shards (3)
- GitHub Check: e2e-shards (2)
- GitHub Check: build-split (docs, linux/amd64)
- GitHub Check: build-split (playground, linux/amd64)
- GitHub Check: build-split (ui, linux/amd64)
- GitHub Check: build-split (worker, linux/amd64)
- GitHub Check: build-split (gateway, linux/amd64)
- GitHub Check: build-split (api, linux/amd64)
- GitHub Check: build-unified (linux/amd64)
- GitHub Check: build / run
- GitHub Check: lint / run
- GitHub Check: test / run
- GitHub Check: autofix
Added API_URL and API_BACKEND_URL to the environment variables in both docker-compose files for improved configuration flexibility.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
infra/docker-compose.split.local.yml(3 hunks)infra/docker-compose.split.yml(3 hunks)infra/split.dockerfile(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- infra/docker-compose.split.yml
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T20:02:21.822Z
Learning: Applies to apps/ui/**/*.{ts,tsx} : Use Next.js App Router with React Server Components for frontend development
📚 Learning: 2025-11-24T20:02:21.822Z
Learnt from: CR
Repo: theopenco/llmgateway PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T20:02:21.822Z
Learning: Run `pnpm build` to ensure production builds work
Applied to files:
infra/split.dockerfile
🪛 Hadolint (2.14.0)
infra/split.dockerfile
[error] 111-111: COPY with more than 2 arguments requires the last argument to end with /
(DL3021)
[error] 112-112: COPY with more than 2 arguments requires the last argument to end with /
(DL3021)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
- GitHub Check: build-split (admin, linux/amd64)
- GitHub Check: build-split (ui, linux/amd64)
- GitHub Check: build-split (playground, linux/amd64)
- GitHub Check: build-split (api, linux/amd64)
- GitHub Check: build-split (docs, linux/amd64)
- GitHub Check: build-split (gateway, linux/amd64)
- GitHub Check: build-unified (linux/amd64)
- GitHub Check: test / run
- GitHub Check: build / run
- GitHub Check: lint / run
- GitHub Check: generate / run
- GitHub Check: e2e-shards (3)
- GitHub Check: e2e-shards (5)
- GitHub Check: e2e-shards (4)
- GitHub Check: e2e-shards (1)
- GitHub Check: e2e-shards (2)
- GitHub Check: autofix
🔇 Additional comments (4)
infra/docker-compose.split.local.yml (2)
127-127: LGTM! ADMIN_URL propagated correctly to all services.The
ADMIN_URLenvironment variable has been consistently added to the ui (line 127), playground (line 162), and docs (line 226) services with the correct default value. This enables these services to reference the admin dashboard URL as needed.Also applies to: 162-162, 226-226
168-198: LGTM! Past issue about missing API_URL and API_BACKEND_URL has been resolved.The admin service now includes the required
API_URLandAPI_BACKEND_URLenvironment variables (lines 194–195). The service configuration is complete with proper build context, port mapping, healthcheck, and network settings, following the established pattern used by other frontend services.infra/split.dockerfile (2)
108-115: Admin-builder stage structure is consistent and well-integrated.The admin-builder follows the established multi-stage build pattern used by other services (api, gateway, ui, playground, worker, docs). Cache mounts, pnpm filter isolation, and build invocation are all correct.
237-252: Admin runtime stage correctly mirrors Next.js app patterns.The admin runtime stage properly replicates the structure of other Next.js applications (ui, playground, docs): standalone output copy, environment configuration, and working directory setup. Port configuration (PORT=80, EXPOSE 80) is consistent with other Next.js runtimes; external port mapping (3006) should be handled via docker-compose or orchestration layer.
Add docker image prune after each build to prevent disk space exhaustion when building multiple images sequentially. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Remove unused system tools (.NET, Android, GHC, CodeQL) - Clean all Docker resources before building - Add disk space monitoring after each build - This should resolve ENOSPC errors in CI 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Test Plan
Summary by CodeRabbit
New Features
Documentation
Infrastructure
Chores
✏️ Tip: You can customize this high-level summary in your review settings.