Skip to content

Feat/analytics dashboard#421

Open
Sendi0011 wants to merge 3 commits intoboundlessfi:mainfrom
Sendi0011:feat/analytics-dashboard
Open

Feat/analytics dashboard#421
Sendi0011 wants to merge 3 commits intoboundlessfi:mainfrom
Sendi0011:feat/analytics-dashboard

Conversation

@Sendi0011
Copy link
Contributor

@Sendi0011 Sendi0011 commented Mar 1, 2026


feat(analytics): implement /me/analytics personal growth dashboard — closes #397


Overview

This PR implements the personal analytics dashboard at app/me/analytics as specified in issue #397. The page is a full-width, interactive growth console that surfaces the authenticated user's platform stats and activity trends using data sourced exclusively from the existing session — no additional API calls are made on page load.


Changes

New files:

  • app/me/analytics/page.tsx — Route entry point, protected by AuthGuard, reads from user.profile populated by the existing getMe() call in useAuthStatus
  • components/analytics/AnalyticsBentoGrid.tsx — Asymmetric bento-style grid with staggered Framer Motion mount animation, surfacing all stats from GetMeResponse.stats with trend badges
  • components/analytics/AnalyticsChart.tsx — Recharts LineChart of GetMeResponse.chart with 7D / 30D / 90D / ALL range toggle, custom tooltip, and cyan→indigo gradient line
  • lib/utils/calculateTrend.ts — Pure utility functions for trend percentage calculation, chart data transformation, and range filtering. Independently testable with no React dependencies

How It Works

All data flows from user.profile which is set by useAuthStatus() via the already-existing getMe() fetch in hooks/use-auth.ts. The analytics page consumes this cached value — it introduces zero new network requests.

Trend percentages are calculated client-side by splitting the chart time series into two equal halves (previous period vs current period) and computing the percentage change. The calculateTrend utility handles all edge cases: zero previous-period values, null/undefined fields, and flat periods.

Chart data transformation (sorting by date, formatting labels) lives in transformChartData() and filterChartByRange() in the utility file — not inline in JSX.


Design

  • Bento grid uses an asymmetric 4-column layout — Global Reputation occupies a col-span-2 row-span-2 hero tile, with supporting metrics in smaller tiles around it
  • Cyan (#06b6d4) to Deep Indigo (#4f46e5) gradient applied to the chart line and active range toggle button, consistent with the Boundless palette
  • Glassmorphism tile treatment: bg-white/[0.03], border-white/[0.06], backdrop-blur-sm
  • Framer Motion stagger animation on bento tiles and fade-up on chart section on mount

Accessibility

  • Bento grid tiles are mirrored in a visually hidden <table> with proper column headers for screen readers
  • Chart section includes a sr-only data table fallback listing every date and activity count in the active range
  • Range toggle buttons use aria-pressed and role="group" with a descriptive label
  • Trend badges carry aria-label describing direction and percentage value

A Note on Chart Data

GetMeResponse.chart exposes a single { date, count } activity time series. The issue referenced a "Projects vs Earnings" multi-line chart, however no earnings field exists in the API response. Fabricating a second series would violate the issue's explicit requirement of no hardcoded or mock data. The chart renders the real activity series. If the backend adds additional series in future, AnalyticsChart is structured to accommodate extra <Line> entries with minimal changes.


Testing Checklist

  • npm run lint passes with no errors
  • npm run build passes with no breaking changes
  • No additional API calls on page load — verified via browser Network tab
  • Bento grid renders correctly across mobile, tablet, and desktop breakpoints
  • Trend badges display correct direction and color for each tile
  • Chart renders with cyan→indigo gradient line
  • Range toggle (7D / 30D / 90D / ALL) filters data and re-renders cleanly
  • Hover tooltips display correct values on chart
  • Chart and tiles animate on mount
  • Screen reader table fallbacks present in DOM
  • Edge cases tested: empty chart array, null stats, zero previous-period values

Screenshots

Desktop view

Screenshot 2026-02-28 at 23 48 36 Screenshot 2026-02-28 at 23 48 45

Mobile view

localhost_3000_me_analytics(iPhone 14 Pro Max) localhost_3000_me_analytics(iPhone 14 Pro Max) (5)

Summary by CodeRabbit

  • New Features
    • Added authentication requirement to the analytics page with automatic sign-in redirect for unauthorized users.
    • Introduced an interactive analytics dashboard displaying key metrics including reputation, followers, contributions, and engagement statistics.
    • Added customizable time-range filters to the activity chart (7-day, 30-day, 90-day, and all-time views).
    • Implemented loading states and graceful handling of missing analytics data.

@Sendi0011 Sendi0011 requested a review from 0xdevcollins as a code owner March 1, 2026 00:33
@vercel
Copy link

vercel bot commented Mar 1, 2026

@Sendi0011 is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements a full-featured analytics dashboard page with client-side authentication guards, displaying user metrics in a bento-grid layout with trend indicators and an interactive time-series chart component, all drawing data from the existing session without additional API calls.

Changes

Cohort / File(s) Summary
Analytics Page & Auth Integration
app/me/analytics/page.tsx
Replaces simple redirect with full client component page. Introduces AnalyticsPage wrapper with AuthGuard, AnalyticsContent with useAuthStatus hook, loading state handling, and error messaging for missing analytics data. Renders AnalyticsBentoGrid and AnalyticsChart when authenticated.
Bento Grid Component
components/analytics/AnalyticsBentoGrid.tsx
New responsive dashboard grid component with animated tiles displaying nine metrics (Global Reputation, Community Score, Projects Created, etc.). Includes TrendBadge sub-component for up/down/flat directional indicators with icons. Uses Framer Motion for staggered animations and memoized trend calculations. Implements accessibility via hidden data table and ARIA labels.
Chart Component
components/analytics/AnalyticsChart.tsx
New interactive time-series chart component with Recharts line graph and stateful range selector (7D, 30D, 90D, All Time presets). Includes data filtering, memoized transformations, custom tooltip, responsive layout, and accessible screen-reader table fallback.
Trend Calculation Utilities
lib/utils/calculateTrend.ts
New utility module with TrendResult interface and five exported functions: calculateTrend (compares current vs. previous period with null/zero handling), calculateChartTrend (splits data halves for trend), transformChartData (formats for Recharts), filterChartByRange (time-window filtering), and date formatting logic. Handles edge cases with sensible defaults.

Sequence Diagram

sequenceDiagram
    participant User
    participant AnalyticsPage as AnalyticsPage<br/>(Wrapper)
    participant AuthGuard
    participant AnalyticsContent
    participant BentoGrid as AnalyticsBentoGrid
    participant Chart as AnalyticsChart
    participant Session

    User->>AnalyticsPage: Visit /me/analytics
    AnalyticsPage->>AuthGuard: Render with children
    AuthGuard->>Session: Check authentication status
    alt Not Authenticated
        AuthGuard->>User: Redirect to /sign-in
    else Authenticated
        AuthGuard->>AnalyticsContent: Render content
        AnalyticsContent->>Session: Extract meData (stats & chart)
        AnalyticsContent->>AnalyticsContent: useAuthStatus for loading state
        alt Loading
            AnalyticsContent->>User: Show loading spinner
        else Data Available
            AnalyticsContent->>BentoGrid: Pass stats & chart
            AnalyticsContent->>Chart: Pass chart data
            BentoGrid->>BentoGrid: Calculate trends (calculateChartTrend)
            BentoGrid->>BentoGrid: Memoize tile configs
            BentoGrid->>User: Render animated tile grid
            Chart->>Chart: Transform & memoize chart data
            Chart->>Chart: Apply range filter (7D/30D/90D/ALL)
            Chart->>User: Render interactive line chart
        else No Analytics Data
            AnalyticsContent->>User: Show missing data message
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • 0xdevcollins

Poem

🐰 A dashboard emerges, with tiles dancing bright,
Trends leap and flutter, painted just right,
Charts whisper stories of growth and motion,
All from the session, no API devotion,
The analytics adventure now comes into sight! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% 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 'Feat/analytics dashboard' clearly references the main feature being implemented - an analytics dashboard - matching the PR objectives and file changes.
Linked Issues check ✅ Passed The PR implements all key objectives from issue #397: full-width dashboard with asymmetric Bento grid, multi-line Recharts with trend calculations, time-range selector (7D/30D/90D/ALL), client-side trend utilities, chart data transformation, responsive design, and accessibility features with screen-reader tables.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #397 requirements. The modified page entry, new Bento and Chart components, and utility functions all serve the analytics dashboard feature with no extraneous modifications.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (5)
app/me/analytics/page.tsx (1)

11-12: Consider using const arrow functions per coding guidelines.

Both AnalyticsContent and AnalyticsPage are function declarations. The coding guidelines prefer const arrow functions with explicit type annotations.

♻️ Suggested refactor
-function AnalyticsContent() {
+const AnalyticsContent = (): JSX.Element => {
...
-export default function AnalyticsPage() {
+const AnalyticsPage = (): JSX.Element => {
...
+export default AnalyticsPage;

As per coding guidelines: "Prefer const arrow functions with explicit type annotations over function declarations".

Also applies to: 54-54

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/analytics/page.tsx` around lines 11 - 12, Convert the function
declarations AnalyticsContent and AnalyticsPage into const arrow functions with
explicit React/Next type annotations; replace "function AnalyticsContent() { ...
}" and "function AnalyticsPage() { ... }" with "const AnalyticsContent: React.FC
= () => { ... }" (or the appropriate NextPage/JSX.Element return type for
AnalyticsPage) and ensure any props or return types are explicitly annotated to
satisfy the coding guidelines.
components/analytics/AnalyticsChart.tsx (1)

82-95: Prefer clsx for conditional classes.

The conditional className uses a ternary operator within the template literal. The coding guidelines recommend using clsx or similar helper functions for cleaner conditional class composition.

♻️ Suggested refactor using clsx
+import clsx from 'clsx';
...
            <button
              key={r}
              onClick={() => setRange(r)}
              aria-pressed={range === r}
-             className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150 ${
-               range === r
-                 ? 'bg-gradient-to-r from-[`#06b6d4`] to-[`#4f46e5`] text-white shadow'
-                 : 'text-zinc-400 hover:text-white'
-             }`}
+             className={clsx(
+               'rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150',
+               range === r
+                 ? 'bg-gradient-to-r from-[`#06b6d4`] to-[`#4f46e5`] text-white shadow'
+                 : 'text-zinc-400 hover:text-white'
+             )}
            >

As per coding guidelines: "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/analytics/AnalyticsChart.tsx` around lines 82 - 95, Replace the
inline template-literal conditional classes in the AnalyticsChart component (the
ranges.map button render that calls setRange and reads range) with clsx: import
clsx at the top, then build the button className using clsx to always include
the shared classes ('rounded-lg px-3 py-1.5 text-xs font-medium transition-all
duration-150') and conditionally include the active classes ('bg-gradient-to-r
from-[`#06b6d4`] to-[`#4f46e5`] text-white shadow') when range === r, otherwise
include the inactive classes ('text-zinc-400 hover:text-white'); keep key,
onClick, aria-pressed and button children unchanged.
components/analytics/AnalyticsBentoGrid.tsx (1)

225-226: Consider using clsx for the conditional text size.

The ternary operator in the className could be replaced with clsx for consistency with coding guidelines.

♻️ Suggested refactor
+import clsx from 'clsx';
...
              <p
-               className={`font-bold tracking-tight text-white ${tile.large ? 'text-4xl' : 'text-2xl'}`}
+               className={clsx(
+                 'font-bold tracking-tight text-white',
+                 tile.large ? 'text-4xl' : 'text-2xl'
+               )}
              >

As per coding guidelines: "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/analytics/AnalyticsBentoGrid.tsx` around lines 225 - 226, Replace
the inline ternary in the JSX className on the <p> element inside the
AnalyticsBentoGrid component with clsx: import clsx if not already imported,
then use clsx to combine the static classes "font-bold tracking-tight
text-white" with a conditional mapping for tile.large to choose "text-4xl" or
"text-2xl" (refer to the variable tile and the <p> element where className is
set) so the conditional class follows the project's clsx convention.
lib/utils/calculateTrend.ts (2)

56-65: Consider the fallback behavior when filtering yields no results.

When filtered is empty, the fallback data.slice(-days) returns the last N data points. This is reasonable, but if days exceeds data.length, it returns the entire array. Ensure this behavior aligns with UX expectations (e.g., showing "No data" vs. showing older data outside the requested range).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/utils/calculateTrend.ts` around lines 56 - 65, In filterChartByRange, the
current fallback returns data.slice(-days) which can return the entire array
when days > data.length; change the fallback so it returns an empty array when
the requested days exceeds data.length (so the caller can show "No data") and
otherwise return the last days entries. Concretely, replace the final return so
it uses filtered.length > 0 ? filtered : (days > data.length ? [] :
data.slice(-days)), keeping the function name filterChartByRange and variables
filtered, days and data as references.

6-9: Consider using const arrow functions per coding guidelines.

The coding guidelines prefer const arrow functions with explicit type annotations over function declarations. This is a stylistic preference and can be deferred.

♻️ Example refactor for one function
-export function calculateTrend(
-  current: number | null | undefined,
-  previous: number | null | undefined
-): TrendResult {
+export const calculateTrend = (
+  current: number | null | undefined,
+  previous: number | null | undefined
+): TrendResult => {

As per coding guidelines: "Prefer const arrow functions with explicit type annotations over function declarations".

Also applies to: 26-28, 39-41, 56-58

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/utils/calculateTrend.ts` around lines 6 - 9, Convert the function
declaration calculateTrend to an exported const arrow function with explicit
parameter and return type annotations (keep the same parameter names and
TrendResult return type) and replace the other function declarations in this
file with the same pattern; ensure you export the consts and preserve original
logic and symbol names (e.g., calculateTrend and the same parameter identifiers
and TrendResult) so tooling and callers remain unaffected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/me/analytics/page.tsx`:
- Around line 11-12: Convert the function declarations AnalyticsContent and
AnalyticsPage into const arrow functions with explicit React/Next type
annotations; replace "function AnalyticsContent() { ... }" and "function
AnalyticsPage() { ... }" with "const AnalyticsContent: React.FC = () => { ... }"
(or the appropriate NextPage/JSX.Element return type for AnalyticsPage) and
ensure any props or return types are explicitly annotated to satisfy the coding
guidelines.

In `@components/analytics/AnalyticsBentoGrid.tsx`:
- Around line 225-226: Replace the inline ternary in the JSX className on the
<p> element inside the AnalyticsBentoGrid component with clsx: import clsx if
not already imported, then use clsx to combine the static classes "font-bold
tracking-tight text-white" with a conditional mapping for tile.large to choose
"text-4xl" or "text-2xl" (refer to the variable tile and the <p> element where
className is set) so the conditional class follows the project's clsx
convention.

In `@components/analytics/AnalyticsChart.tsx`:
- Around line 82-95: Replace the inline template-literal conditional classes in
the AnalyticsChart component (the ranges.map button render that calls setRange
and reads range) with clsx: import clsx at the top, then build the button
className using clsx to always include the shared classes ('rounded-lg px-3
py-1.5 text-xs font-medium transition-all duration-150') and conditionally
include the active classes ('bg-gradient-to-r from-[`#06b6d4`] to-[`#4f46e5`]
text-white shadow') when range === r, otherwise include the inactive classes
('text-zinc-400 hover:text-white'); keep key, onClick, aria-pressed and button
children unchanged.

In `@lib/utils/calculateTrend.ts`:
- Around line 56-65: In filterChartByRange, the current fallback returns
data.slice(-days) which can return the entire array when days > data.length;
change the fallback so it returns an empty array when the requested days exceeds
data.length (so the caller can show "No data") and otherwise return the last
days entries. Concretely, replace the final return so it uses filtered.length >
0 ? filtered : (days > data.length ? [] : data.slice(-days)), keeping the
function name filterChartByRange and variables filtered, days and data as
references.
- Around line 6-9: Convert the function declaration calculateTrend to an
exported const arrow function with explicit parameter and return type
annotations (keep the same parameter names and TrendResult return type) and
replace the other function declarations in this file with the same pattern;
ensure you export the consts and preserve original logic and symbol names (e.g.,
calculateTrend and the same parameter identifiers and TrendResult) so tooling
and callers remain unaffected.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4ec4081 and 3a59c4c.

📒 Files selected for processing (4)
  • app/me/analytics/page.tsx
  • components/analytics/AnalyticsBentoGrid.tsx
  • components/analytics/AnalyticsChart.tsx
  • lib/utils/calculateTrend.ts

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.

Implementation of "Analytics" Page

1 participant