Skip to content

Add admin UI for platform statistics and business verification - #264

Merged
MihuNt3r merged 3 commits into
mainfrom
feature/ui-for-statistics-and-verify-business-accounts
Jun 22, 2026
Merged

Add admin UI for platform statistics and business verification#264
MihuNt3r merged 3 commits into
mainfrom
feature/ui-for-statistics-and-verify-business-accounts

Conversation

@MihuNt3r

@MihuNt3r MihuNt3r commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator
  • 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)

Summary by CodeRabbit

  • New Features
    • Added admin Statistics and Business Verification pages with protected routing, paginated pending-business review (approve/reject with required reason), and a metrics dashboard.
    • Added realtime notification updates via Server-Sent Events and “Mark all as read” actions.
  • Improvements
    • Updated notification UI to support marking individual notifications as read and properly reflect read state from varying payloads.
  • Chores
    • Refreshed theme token wiring for consistent light/dark utility colors.

- 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]>
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@MihuNt3r, we couldn't start this review because you've reached your PR review rate limit.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2890505d-de89-4fcb-9be7-d2c68d860929

📥 Commits

Reviewing files that changed from the base of the PR and between a6f4946 and 2423505.

📒 Files selected for processing (2)
  • frontend/src/hooks/useRealtimeNotifications.ts
  • scripts/bootstrap-localstack.sh
📝 Walkthrough

Walkthrough

Two new admin-only pages are added: AdminStatisticsPage displays aggregated platform metrics in a card dashboard, and AdminPendingBusinessesPage lists pending businesses for approval or rejection with pagination and a confirmation dialog. The admin service migrates from Redis notification publishing to an outbox-based event pattern for business review events. Realtime SSE notifications and mark-as-read functionality are added across the frontend. Supporting changes include new API types, three apiClient methods, protected routes, expanded sidebar navigation with the realtime hook, a Tailwind @theme inline token block, LocalStack Docker Compose infrastructure, a bootstrap script for local SNS/SQS wiring, and refactored admin-auth-api startup.

Changes

Admin Statistics & Business Verification

Layer / File(s) Summary
API type contracts
frontend/src/types/api.ts
Adds PlatformStatistics (aggregate metrics), PendingBusinessListItem, PaginatedPendingBusinesses, ReviewBusinessStatus ("verified" | "rejected"), and PaginationParams interfaces.
Admin API client methods
frontend/src/api/client.ts
Imports new types and adds adminGetStatistics, adminGetPendingBusinesses, and adminReviewBusiness to apiClient.
AdminStatisticsPage
frontend/src/pages/guest/Admin/AdminStatisticsPage.tsx
Implements dashboard: formatValue and buildSections helpers, useEffect async load with mounted guard, highlights aggregation, and metric card-grid rendering across four sections.
AdminPendingBusinessesPage
frontend/src/pages/guest/Admin/AdminPendingBusinessesPage.tsx
Implements business review flow: paginated fetch lifecycle, approve/reject handlers with actingId busy state, clickable business cards, pagination controls, and rejection confirmation dialog with textarea validation and spinner.
Route wiring, sidebar, and theme tokens
frontend/src/App.tsx, frontend/src/components/ui/Sidebar.tsx, frontend/src/App.css
Adds /admin/statistics and /admin/businesses as RequireAdmin-guarded routes, expands admin sidebar to three links (Users/Statistics/Verify Businesses), integrates useRealtimeNotifications() in Sidebar, and adds @theme inline Tailwind design-token mapping block.
Admin service outbox event wiring
internal/admin-auth/service/admin/admin_service.go
Replaces Redis notification broker with outbox pattern: NewService accepts outboxWriter, ReviewBusinessStatus builds and enqueues outbox.Event with UTC-timestamp-based IDs instead of publishing notifications.
Outbox event types and SNS config
pkg/outbox/message.go, pkg/outbox/sns_publisher.go
Adds EventTypeBusinessVerified and EventTypeBusinessRejected constants; updates NewSNSPublisher to accept optional endpointURL for LocalStack endpoint override.

Realtime Notifications & Mark as Read

Layer / File(s) Summary
Realtime SSE notifications hook
frontend/src/hooks/useRealtimeNotifications.ts
Adds useRealtimeNotifications hook: opens EventSource for SSE stream, invalidates ["notifications"] query on incoming messages, handles errors with 5-second reconnect backoff and unmount guard, cleans up stream and timers on unmount.
Notification bell mark-as-read
frontend/src/components/Notifications/NotificationBell.tsx
Adds useMutation for markNotificationsRead, derives unreadIds, wires conditional "Mark all as read" button with spinner, and invalidates notifications query on success.
Notifications page mark-as-read
frontend/src/pages/guest/Notifications/NotificationsPage.tsx
Adds useMutation and useQueryClient for mark-as-read; renders "Mark all as read" button and per-notification "Mark as read" buttons (disabled while pending).
Notification field mapping
frontend/src/api/notifications.ts
Broadens mapNotification to derive read from isRead, is_read, or read for backend payload compatibility.

Local Development Infrastructure

Layer / File(s) Summary
Admin auth startup refactoring
cmd/admin-auth-api/main.go
Removes Redis configuration, notification broker setup, and resilience policy; updates adminsvc.NewService to wire outboxWriter instead of broker.
Outbox worker SNS endpoint
cmd/outbox-worker/main.go
Reads OUTBOX_SNS_ENDPOINT_URL from secrets and passes endpoint to NewSNSPublisher for local development.
LocalStack infrastructure
build/compose.infra.yaml, scripts/bootstrap-localstack.sh
Adds LocalStack v3 service (SNS/SQS on port 4566, persistent volume, healthcheck); provides bootstrap script that waits for health, creates SNS topic and SQS queue, subscribes queue with eventType filter policy, and outputs .env variables.
Build targets and configuration
Makefile, .gitignore, terraform/main.tf
Adds run-notifications and run-outbox Makefile targets; ignores pkg/gateway/admin/client/; updates Terraform SNS→SQS filter_policy to include business_verified and business_rejected event types.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Suggested reviewers

  • mblinovv
  • viktorzhabskyi
  • DmyMi

Poem

🐇 Hop, hop — the admin dashboard springs to life,
Statistics gleam through cards so bright and rife!
Pending businesses queue for review,
Approve, reject, with outbox events shining through.
Realtime notifications dance and spin,
SSE streams and mark-as-read begin! 📊✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding admin UI for platform statistics and business verification. It directly reflects the primary features introduced in this changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/ui-for-statistics-and-verify-business-accounts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@MihuNt3r
MihuNt3r requested a review from Vadz-Danil June 20, 2026 10:41

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e5b4328 and ee566a8.

📒 Files selected for processing (7)
  • frontend/src/App.css
  • frontend/src/App.tsx
  • frontend/src/api/client.ts
  • frontend/src/components/ui/Sidebar.tsx
  • frontend/src/pages/guest/Admin/AdminPendingBusinessesPage.tsx
  • frontend/src/pages/guest/Admin/AdminStatisticsPage.tsx
  • frontend/src/types/api.ts

Comment thread frontend/src/App.css
Comment thread frontend/src/pages/guest/Admin/AdminPendingBusinessesPage.tsx
Comment thread frontend/src/pages/guest/Admin/AdminPendingBusinessesPage.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 lift

Make the review update and outbox insert succeed or fail together.

ReviewBusiness persists 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-pending conflict 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 | 🟠 Major

Scope 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

📥 Commits

Reviewing files that changed from the base of the PR and between ee566a8 and a6f4946.

📒 Files selected for processing (16)
  • .gitignore
  • Makefile
  • build/compose.infra.yaml
  • cmd/admin-auth-api/main.go
  • cmd/outbox-worker/main.go
  • frontend/src/api/notifications.ts
  • frontend/src/components/Notifications/NotificationBell.tsx
  • frontend/src/components/ui/Sidebar.tsx
  • frontend/src/hooks/useRealtimeNotifications.ts
  • frontend/src/pages/guest/Admin/AdminPendingBusinessesPage.tsx
  • frontend/src/pages/guest/Notifications/NotificationsPage.tsx
  • internal/admin-auth/service/admin/admin_service.go
  • pkg/outbox/message.go
  • pkg/outbox/sns_publisher.go
  • scripts/bootstrap-localstack.sh
  • terraform/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

Comment thread frontend/src/hooks/useRealtimeNotifications.ts Outdated
Comment thread scripts/bootstrap-localstack.sh
Comment thread scripts/bootstrap-localstack.sh
@MihuNt3r
MihuNt3r merged commit 0fb95bb into main Jun 22, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants