Skip to content

feat: add online users counter#163

Merged
ViktorSvertoka merged 2 commits into
developfrom
yn/feat/users-counter
Jan 19, 2026
Merged

feat: add online users counter#163
ViktorSvertoka merged 2 commits into
developfrom
yn/feat/users-counter

Conversation

@YNazymko12

@YNazymko12 YNazymko12 commented Jan 19, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR adds a real-time online users counter that displays at the bottom-right of the homepage. The counter shows how many users are currently active on the platform with motivational messages that encourage learning and preparation for interviews. The implementation uses PostgreSQL session tracking with automatic cleanup and includes smooth animations with theme adaptation.


Related Issue

Issue: N/A (New feature)


Changes

  • Added /api/sessions/activity POST endpoint for session tracking
  • Implemented PostgreSQL-based session storage using existing active_sessions table
  • Created OnlineCounterPopup component with animated gradient design
  • Added motivational messages that change based on active user count
  • Implemented automatic session cleanup (15-minute timeout, 5% probability)
  • Added sessionStorage check to prevent popup re-showing on navigation
  • Configured httpOnly cookies for session persistence (30-day expiry)
  • Included animations: progress bar (10s), spinning sparkles (3s), pulsing indicator

Summary by CodeRabbit

  • New Features

    • Added an online user counter popup that displays the current number of active users in the app with animated entrance and decorative styling.
    • Counter respects per-session display preferences to avoid repeated notifications.
  • Style

    • Added new animation keyframes for enhanced visual transitions.

✏️ Tip: You can customize this high-level summary in your review settings.

…online counter on Home page

- Add session tracking API endpoint with PostgreSQL storage
- Display animated popup showing active users count
- Show motivational messages based on user activity
- Include automatic session cleanup (15-min timeout)
@netlify

netlify Bot commented Jan 19, 2026

Copy link
Copy Markdown

Deploy Preview for develop-devlovers ready!

Name Link
🔨 Latest commit f959569
🔍 Latest deploy log https://app.netlify.com/projects/develop-devlovers/deploys/696e18950aa8ff0008833964
😎 Deploy Preview https://deploy-preview-163--develop-devlovers.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jan 19, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR introduces a real-time online user counter feature. A new API route tracks user sessions with activity timestamps, performs probabilistic cleanup of stale sessions, and returns the count of active users. A new React component fetches and displays this count with animations and visual styling.

Changes

Cohort / File(s) Summary
Layout & Component Integration
frontend/app/[locale]/layout.tsx, frontend/components/shared/OnlineCounterPopup.tsx
Adds OnlineCounterPopup component to layout and creates new client component that fetches online user count from API on mount, displays animated popup with emoji/text based on count range, uses sessionStorage for one-time display flag, handles request failures gracefully.
API Route - Session Tracking
frontend/app/api/sessions/activity/route.ts
Introduces POST endpoint that retrieves/generates sessionId from cookie, upserts session record with current timestamp, performs probabilistic cleanup (5%) of sessions inactive for 15+ minutes, counts distinct active sessions, returns online count, sets secure HTTP-only cookie for 30 days.
Styling - Animations
frontend/app/globals.css
Adds two new CSS keyframes: progress (width 100% → 0%) and shrink (scale 1 → 0) for animation support.

Sequence Diagram

sequenceDiagram
    participant User as User Browser
    participant Layout as LocaleLayout
    participant Component as OnlineCounterPopup
    participant API as /api/sessions/activity
    participant DB as activeSessions DB

    User->>Layout: Load page
    Layout->>Component: Mount OnlineCounterPopup
    Component->>Component: Initialize state
    Component->>API: POST /api/sessions/activity
    
    API->>API: Retrieve/generate sessionId
    API->>DB: Upsert session with timestamp
    API->>DB: Delete old sessions (5% chance)
    API->>DB: Count distinct sessions < 15min old
    API->>API: Set secure HTTP-only cookie
    API-->>Component: {online: count}
    
    Component->>Component: Store count in state
    Component->>Component: Set show flag after delay
    Component->>User: Render animated popup with count
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • AM1007
  • ViktorSvertoka

Poem

🐰✨ A counter hops to life so bright,
Tracking souls both day and night,
Sessions dance in database rows,
Online counts in real-time flows,
Watch them sparkle, watch them glow! 💫

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add online users counter' directly and clearly summarizes the main change—introducing a new online users counter feature with API endpoint, database session tracking, and frontend popup component.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@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

🤖 Fix all issues with AI agents
In `@frontend/app/api/sessions/activity/route.ts`:
- Around line 30-38: The fire-and-forget cleanup call can be killed by
serverless request termination; modify the logic around
db.delete(activeSessions).where(lt(activeSessions.lastActivity,
cleanupThreshold)) so the cleanup is awaited (or moved to a scheduled/background
job instead) while still swallowing errors—i.e., await the delete promise and
catch/log errors (using the existing catch pattern) or extract this cleanup into
a cron/worker; reference the symbols activeSessions, lastActivity,
SESSION_TIMEOUT_MINUTES and db.delete to locate and update the code.

In `@frontend/components/shared/OnlineCounterPopup.tsx`:
- Around line 10-21: In OnlineCounterPopup's useEffect: remove the early return
that blocks the fetch so the POST to '/api/sessions/activity' always runs (so
lastActivity is refreshed), but still gate the UI show/hide using
sessionStorage.getItem('shown') when deciding whether to call setShow; store the
timeout IDs returned from setTimeout in local variables, use a cleanup function
to clear those timeouts and set an "unmounted" flag (or check mounted state) to
avoid calling setShow/setOnline after unmount, and ensure
sessionStorage.setItem('shown','1') is only used to prevent future UI displays,
not to skip the API ping.
- Line 24: The component currently hides the popup for falsy online counts;
update OnlineCounterPopup.tsx to use an explicit null check on the `online`
prop/variable (replace `if (!online) return null;` with `if (online === null)
return null;`) so that a valid value of 0 is rendered (and adjust the displayed
copy/emoji in the render path to correctly show "0 online" where appropriate).

Comment on lines +30 to +38
if (Math.random() < 0.05) {
const cleanupThreshold = new Date(
Date.now() - SESSION_TIMEOUT_MINUTES * 60 * 1000
);

db.delete(activeSessions)
.where(lt(activeSessions.lastActivity, cleanupThreshold))
.catch(err => console.error('Cleanup error:', err));
}

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.

⚠️ Potential issue | 🟠 Major

Cleanup may never run if the request ends early.
In serverless runtimes, un-awaited DB calls can be terminated when the response returns, leaving stale sessions uncollected and the table growing. Consider awaiting the cleanup (or moving it to a scheduled job) while still swallowing errors to keep the request resilient.

🛠️ Suggested fix (await cleanup safely)
-    if (Math.random() < 0.05) {
-      const cleanupThreshold = new Date(
-        Date.now() - SESSION_TIMEOUT_MINUTES * 60 * 1000
-      );
-
-      db.delete(activeSessions)
-        .where(lt(activeSessions.lastActivity, cleanupThreshold))
-        .catch(err => console.error('Cleanup error:', err));
-    }
+    if (Math.random() < 0.05) {
+      const cleanupThreshold = new Date(
+        Date.now() - SESSION_TIMEOUT_MINUTES * 60 * 1000
+      );
+
+      try {
+        await db
+          .delete(activeSessions)
+          .where(lt(activeSessions.lastActivity, cleanupThreshold));
+      } catch (err) {
+        console.error('Cleanup error:', err);
+      }
+    }
🤖 Prompt for AI Agents
In `@frontend/app/api/sessions/activity/route.ts` around lines 30 - 38, The
fire-and-forget cleanup call can be killed by serverless request termination;
modify the logic around
db.delete(activeSessions).where(lt(activeSessions.lastActivity,
cleanupThreshold)) so the cleanup is awaited (or moved to a scheduled/background
job instead) while still swallowing errors—i.e., await the delete promise and
catch/log errors (using the existing catch pattern) or extract this cleanup into
a cron/worker; reference the symbols activeSessions, lastActivity,
SESSION_TIMEOUT_MINUTES and db.delete to locate and update the code.

Comment on lines +10 to +21
useEffect(() => {
if (sessionStorage.getItem('shown')) return;

fetch('/api/sessions/activity', { method: 'POST' })
.then(r => r.json())
.then(data => {
setOnline(data.online);
setTimeout(() => setShow(true), 500);
sessionStorage.setItem('shown', '1');
setTimeout(() => setShow(false), 10000);
})
.catch(() => setOnline(null));

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.

⚠️ Potential issue | 🟠 Major

Session tracking stops after the first display in a tab.
The early sessionStorage return prevents subsequent POSTs, so lastActivity isn’t refreshed and long-lived sessions will fall out of the 15‑minute window even while the user is active. You can still call the API while gating only the UI display. Also consider cleaning up timeouts to avoid state updates after unmount.

🛠️ Suggested fix (always ping, gate UI, and clear timers)
-  useEffect(() => {
-    if (sessionStorage.getItem('shown')) return;
-
-    fetch('/api/sessions/activity', { method: 'POST' })
-      .then(r => r.json())
-      .then(data => {
-        setOnline(data.online);
-        setTimeout(() => setShow(true), 500);
-        sessionStorage.setItem('shown', '1');
-        setTimeout(() => setShow(false), 10000);
-      })
-      .catch(() => setOnline(null));
-  }, []);
+  useEffect(() => {
+    const alreadyShown = sessionStorage.getItem('shown');
+    let showTimer: ReturnType<typeof setTimeout> | undefined;
+    let hideTimer: ReturnType<typeof setTimeout> | undefined;
+
+    fetch('/api/sessions/activity', { method: 'POST' })
+      .then(r => r.json())
+      .then(data => {
+        setOnline(data.online);
+        if (!alreadyShown) {
+          showTimer = setTimeout(() => setShow(true), 500);
+          sessionStorage.setItem('shown', '1');
+          hideTimer = setTimeout(() => setShow(false), 10000);
+        }
+      })
+      .catch(() => setOnline(null));
+
+    return () => {
+      if (showTimer) clearTimeout(showTimer);
+      if (hideTimer) clearTimeout(hideTimer);
+    };
+  }, []);
🤖 Prompt for AI Agents
In `@frontend/components/shared/OnlineCounterPopup.tsx` around lines 10 - 21, In
OnlineCounterPopup's useEffect: remove the early return that blocks the fetch so
the POST to '/api/sessions/activity' always runs (so lastActivity is refreshed),
but still gate the UI show/hide using sessionStorage.getItem('shown') when
deciding whether to call setShow; store the timeout IDs returned from setTimeout
in local variables, use a cleanup function to clear those timeouts and set an
"unmounted" flag (or check mounted state) to avoid calling setShow/setOnline
after unmount, and ensure sessionStorage.setItem('shown','1') is only used to
prevent future UI displays, not to skip the API ping.

.catch(() => setOnline(null));
}, []);

if (!online) return null;

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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n frontend/components/shared/OnlineCounterPopup.tsx

Repository: DevLoversTeam/devlovers.net

Length of output: 4716


🏁 Script executed:

fd -t f -e ts -e tsx -e js -e json | xargs rg -l "sessions/activity|online" | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 255


🏁 Script executed:

cat -n frontend/app/api/sessions/activity/route.ts

Repository: DevLoversTeam/devlovers.net

Length of output: 2324


Use an explicit null check instead of falsy check to support displaying "0 online."
The API returns 0 when there are no active sessions (line 52 of route.ts), but if (!online) hides the popup for this valid value. Change to if (online === null) return null; if you want to display "0 online" with appropriate copy/emoji.

🤖 Prompt for AI Agents
In `@frontend/components/shared/OnlineCounterPopup.tsx` at line 24, The component
currently hides the popup for falsy online counts; update OnlineCounterPopup.tsx
to use an explicit null check on the `online` prop/variable (replace `if
(!online) return null;` with `if (online === null) return null;`) so that a
valid value of 0 is rendered (and adjust the displayed copy/emoji in the render
path to correctly show "0 online" where appropriate).

@ViktorSvertoka ViktorSvertoka merged commit 9be8fcc into develop Jan 19, 2026
9 checks passed
@ViktorSvertoka ViktorSvertoka deleted the yn/feat/users-counter branch January 19, 2026 11:54
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