Add admin UI for platform statistics and business verification - #264
Conversation
- Add Statistics page showing aggregated platform metrics (users, account status, guest and business activity) - Add Verify Businesses page with paginated pending list and an approve/reject review flow (rejection requires a reason) - Wire up /admin/statistics and /admin/businesses routes and add sidebar nav links - Map shadcn design tokens via @theme inline so popover/muted/etc. utilities render correctly (fixes transparent dialog surface) Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
Warning Review limit reached
More reviews will be available in 43 minutes and 10 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughTwo new admin-only pages are added: ChangesAdmin Statistics & Business Verification
Realtime Notifications & Mark as Read
Local Development Infrastructure
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Possibly related PRs
Suggested reviewers
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)
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: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/App.css`:
- Line 13: Resolve the mismatch between the Tailwind v4 `@theme inline` syntax
used in App.css and the stylelint configuration by taking one of two approaches:
either add tailwindcss as a dependency in package.json to match the code's
usage, or update the .stylelintrc.json configuration file to modify the
scss/at-rule-no-unknown rule to include an ignoreAtRules array that excludes
both theme and custom-variant at-rules from being flagged as unknown. Choose the
approach that best aligns with your project's actual Tailwind setup.
In `@frontend/src/pages/guest/Admin/AdminPendingBusinessesPage.tsx`:
- Around line 69-72: The removeFromList function updates the totalCount when
items are removed but does not validate whether the current page index remains
valid. When the last item on a trailing page is removed, users can be stranded
on an empty page. After the setTotalCount call in removeFromList, add logic to
clamp the page state by calculating the maximum valid page index based on the
new total count and items per page. If the current page exceeds this maximum,
reset it to the highest valid page number to prevent users from viewing empty
pages when content still exists on earlier pages. Apply the same normalization
logic to the other removal handlers mentioned (lines 107-108 and 138-145).
- Around line 33-34: The current implementation only tracks a single actingId,
which allows multiple concurrent mutations on different cards and causes race
conditions. Replace the single actingId state variable with a Set-based state
(e.g., actingIds) that can track multiple in-flight request IDs simultaneously.
Update all action handlers (in the areas around lines 74-85, 148-149, and
187-206) to add the business ID to this Set when a mutation starts and remove it
when the mutation completes. Modify all disabled state checks to verify whether
a specific business ID exists in the actingIds Set rather than comparing against
a single value, ensuring that only the cards with in-flight requests are
disabled while others remain actionable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7c099356-5ef7-42b2-86fc-bc0ad6f05773
📒 Files selected for processing (7)
frontend/src/App.cssfrontend/src/App.tsxfrontend/src/api/client.tsfrontend/src/components/ui/Sidebar.tsxfrontend/src/pages/guest/Admin/AdminPendingBusinessesPage.tsxfrontend/src/pages/guest/Admin/AdminStatisticsPage.tsxfrontend/src/types/api.ts
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
internal/admin-auth/service/admin/admin_service.go (1)
224-264:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftMake the review update and outbox insert succeed or fail together.
ReviewBusinesspersists the status before the outbox row is inserted, and Line 262 only logs enqueue failures before returning success. A transient outbox insert failure leaves the business reviewed without a stored event, and a retry will hit the non-pendingconflict path. Move the outbox write into the same transaction as the status update, or have the business review operation own the outbox insert, and propagate failures instead of swallowing them.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/admin-auth/service/admin/admin_service.go` around lines 224 - 264, The ReviewBusiness call updates the business status independently from the outbox insert that happens after it, and errors from the outboxWriter.Enqueue call are only logged without being returned, creating a risk of inconsistent state if the enqueue fails. Either move the outbox write into the same transaction as the ReviewBusiness method call so both operations succeed or fail together, or propagate the error returned by s.outboxWriter.Enqueue instead of only logging it in the error handling block, so the caller knows the operation failed.frontend/src/components/Notifications/NotificationBell.tsx (1)
25-36:⚠️ Potential issue | 🟠 MajorScope notification cache keys by identity and query params.
NotificationBell fetches with limit=20 and NotificationsPage with limit=50, but both use identical
["notifications"]cache key. This causes cache collisions where switching between components could briefly display the wrong dataset. Additionally, the cache key doesn't include the token, creating a multi-user risk if sessions share the same client instance.🔧 Proposed fix
+ const notificationsScopeKey = ["notifications", token] as const; + const notificationsKey = ["notifications", token, 20] as const; + const { data: notifications = [] } = useQuery({ - queryKey: ["notifications"], + queryKey: notificationsKey, queryFn: () => fetchNotifications(token!, 20), refetchInterval: 30000, refetchIntervalInBackground: false, enabled: !!token, }); const markRead = useMutation({ mutationFn: (ids: string[]) => markNotificationsRead(token!, ids), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["notifications"] }), + onSuccess: () => queryClient.invalidateQueries({ queryKey: notificationsScopeKey }), });Apply similar changes to NotificationsPage (with limit=50) and useRealtimeNotifications.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/Notifications/NotificationBell.tsx` around lines 25 - 36, The cache key for the useQuery call in NotificationBell is hardcoded as ["notifications"] without including the token or the limit parameter, causing cache collisions with other components that fetch notifications with different limits or users. Update the queryKey to include both the token and the limit value (20 in this case) to create a unique cache key per user and query parameters, such as ["notifications", token, 20]. Apply the same scoped cache key pattern to the markRead mutation's onSuccess invalidation and ensure NotificationsPage (which uses limit=50) and useRealtimeNotifications also use similarly scoped cache keys that include their respective tokens and limit values to prevent multi-user and parameter-based cache collisions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/hooks/useRealtimeNotifications.ts`:
- Around line 15-49: The useEffect hook establishing the SSE connection in the
connect function only depends on queryClient in its dependency array, but it
reads the authentication token from localStorage inside the effect. When the
token changes due to login/logout, the effect won't re-run and the SSE stream
becomes stale. Extract the token reading outside the useEffect hook (before it)
and add the token value to the dependency array so that the entire effect
re-runs whenever the authentication token changes, ensuring the SSE connection
always uses the current token.
In `@scripts/bootstrap-localstack.sh`:
- Around line 8-9: The script claims reruns are idempotent, but the sns
subscribe commands (in the section around lines 60-73) can create duplicate
subscriptions to the same topic/queue on each rerun, causing duplicate
notifications. Add conditional checks before each sns subscribe command to
verify the subscription does not already exist. You can use aws sns
list-subscriptions-by-topic to check for existing subscriptions and only call
sns subscribe if the subscription is not already present. Alternatively, update
the comment at lines 8-9 to accurately reflect that the script is not fully
idempotent due to the subscribe operations.
- Around line 31-34: The until loop that checks docker health status using
docker inspect and waits for the healthy state has no timeout mechanism, which
can cause the script to hang indefinitely if LocalStack never becomes healthy.
Modify the health check loop to include a maximum retry counter or timeout
value, and add a fail condition that exits the script with a clear error message
if the health check does not succeed within the specified timeframe. Reference
the container health status check logic and implement an exit with appropriate
error handling before the loop completes all retries.
---
Outside diff comments:
In `@frontend/src/components/Notifications/NotificationBell.tsx`:
- Around line 25-36: The cache key for the useQuery call in NotificationBell is
hardcoded as ["notifications"] without including the token or the limit
parameter, causing cache collisions with other components that fetch
notifications with different limits or users. Update the queryKey to include
both the token and the limit value (20 in this case) to create a unique cache
key per user and query parameters, such as ["notifications", token, 20]. Apply
the same scoped cache key pattern to the markRead mutation's onSuccess
invalidation and ensure NotificationsPage (which uses limit=50) and
useRealtimeNotifications also use similarly scoped cache keys that include their
respective tokens and limit values to prevent multi-user and parameter-based
cache collisions.
In `@internal/admin-auth/service/admin/admin_service.go`:
- Around line 224-264: The ReviewBusiness call updates the business status
independently from the outbox insert that happens after it, and errors from the
outboxWriter.Enqueue call are only logged without being returned, creating a
risk of inconsistent state if the enqueue fails. Either move the outbox write
into the same transaction as the ReviewBusiness method call so both operations
succeed or fail together, or propagate the error returned by
s.outboxWriter.Enqueue instead of only logging it in the error handling block,
so the caller knows the operation failed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e6fb688d-d9c3-4231-98af-d594573fd67d
📒 Files selected for processing (16)
.gitignoreMakefilebuild/compose.infra.yamlcmd/admin-auth-api/main.gocmd/outbox-worker/main.gofrontend/src/api/notifications.tsfrontend/src/components/Notifications/NotificationBell.tsxfrontend/src/components/ui/Sidebar.tsxfrontend/src/hooks/useRealtimeNotifications.tsfrontend/src/pages/guest/Admin/AdminPendingBusinessesPage.tsxfrontend/src/pages/guest/Notifications/NotificationsPage.tsxinternal/admin-auth/service/admin/admin_service.gopkg/outbox/message.gopkg/outbox/sns_publisher.goscripts/bootstrap-localstack.shterraform/main.tf
✅ Files skipped from review due to trivial changes (1)
- pkg/outbox/message.go
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/pages/guest/Admin/AdminPendingBusinessesPage.tsx
Summary by CodeRabbit