Feat/analytics dashboard#421
Conversation
|
@Sendi0011 is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughReplaces a redirect with a client-side, auth-guarded AnalyticsPage that reads session Changes
Sequence DiagramsequenceDiagram
participant User
participant AnalyticsPage as AnalyticsPage
participant AuthGuard
participant Session
participant AnalyticsContent
participant BentoGrid as AnalyticsBentoGrid
participant Chart as AnalyticsChart
User->>AnalyticsPage: Visit /me/analytics
AnalyticsPage->>AuthGuard: Render children
AuthGuard->>Session: Read session / me
alt Not authenticated
AuthGuard->>User: Redirect to /sign-in
else Authenticated
AuthGuard->>AnalyticsContent: Render
AnalyticsContent->>Session: Extract me.stats & me.chart
AnalyticsContent->>User: Show loading spinner while auth loading
alt No analytics data
AnalyticsContent->>User: Show missing-data message
else Has analytics data
AnalyticsContent->>BentoGrid: Provide stats & chart
AnalyticsContent->>Chart: Provide chart data
BentoGrid->>lib/utils/calculateTrend: compute tile trends
Chart->>lib/utils/calculateTrend: transform & filter chart data
BentoGrid->>User: Render animated bento tiles
Chart->>User: Render interactive line chart
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 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.
🧹 Nitpick comments (5)
app/me/analytics/page.tsx (1)
11-12: Consider using const arrow functions per coding guidelines.Both
AnalyticsContentandAnalyticsPageare 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
filteredis empty, the fallbackdata.slice(-days)returns the last N data points. This is reasonable, but ifdaysexceedsdata.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.
📒 Files selected for processing (4)
app/me/analytics/page.tsxcomponents/analytics/AnalyticsBentoGrid.tsxcomponents/analytics/AnalyticsChart.tsxlib/utils/calculateTrend.ts
There was a problem hiding this comment.
Hi @Sendi0011
Here are my correction
-
Card Component Alignment
I noticed that the analytics cards are using custom bento-style gradients (cyan and indigo). These should be aligned with our existing dashboard design.- Check the
/me/earningsdashboard. - Use the standard
Cardcomponent fromcomponents/ui/cardfor consistency.
- Check the
-
Gradient and Color Usage
- You used skyblue and indigo gradients instead of our primary neon green.
- Our platform's primary color is
#a7f950. - Use the standard Tailwind utility classes:
text-primarybg-primaryborder-primary
- Reference: These variables are already defined in
globals.cssand mapped to our brand identity.
-
API Data Integration
- Currently, only
statsandchartobjects from the API response are utilized. - For a full analytics dashboard, also integrate
activitiesGraphandrecentActivities. - Reuse these existing files to save time and maintain consistency:
components/profile/ActivityHeatMap.tsx: For the contribution graph (heatmap).components/profile/ActivityFeed.tsx: For the recent activity list.- For "Activity Over Time" Section, reuse
components/chart-area-interactive.tsx
- Currently, only
-
Page Layout and Styling
- The page container is currently using a custom light background and border radius.
- Follow the standard page layout structure.
- Apply the following classes on your main wrapper to align with the sidebar and other dashboard pages:
className="container mx-auto space-y-8 py-8 px-6"
-
For Full Integration Testing
- Go to /hackathons and join an ongoing hackathon.
- Go to /projects, comment on projects, and vote on them.
- Return to /me/analytics to verify that these actions reflect in the statistics and activity feeds.
Note: We prefer a video evidence
…rimary color, and integrate activity feeds
There was a problem hiding this comment.
🧹 Nitpick comments (7)
components/analytics/AnalyticsBentoGrid.tsx (3)
27-59: Use typed const arrow functions for component declarations.
TrendBadgeandAnalyticsBentoGridshould match the TSX function style expected by the repo.♻️ Suggested refactor
-function TrendBadge({ trend }: { trend: TrendResult }) { +const TrendBadge = ({ + trend, +}: { + trend: TrendResult; +}): JSX.Element => { ... -} +}; -export function AnalyticsBentoGrid({ stats, chart }: Props) { +export const AnalyticsBentoGrid = ({ + stats, + chart, +}: Props): JSX.Element => { ... -} +};As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".
Also applies to: 85-230
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/analytics/AnalyticsBentoGrid.tsx` around lines 27 - 59, Change the component declarations from function declarations to typed const arrow functions: replace the function TrendBadge({ trend }: { trend: TrendResult }) { ... } with a const TrendBadge: React.FC<{ trend: TrendResult }> = ({ trend }) => { ... } (and do the same for AnalyticsBentoGrid, using an appropriate props interface/type and explicit React.FC or React.FunctionComponent annotation). Ensure all React imports/types remain available (e.g., React, TrendResult) and update any exported names to use the const bindings so the components keep the same external API.
87-89:flatobject identity breakstilesmemoization effectiveness.Because
flatis recreated every render, Line 165 always changes and forcestilesrecomputation.♻️ Suggested refactor
+const FLAT_TREND: TrendResult = { percentage: 0, direction: 'flat' }; ... export function AnalyticsBentoGrid({ stats, chart }: Props) { const chartTrend = useMemo(() => calculateChartTrend(chart), [chart]); - const flat: TrendResult = { percentage: 0, direction: 'flat' }; ... - trend: flat, + trend: FLAT_TREND, ... - trend: flat, + trend: FLAT_TREND, ... - trend: flat, + trend: FLAT_TREND, ... - trend: flat, + trend: FLAT_TREND, ... - trend: flat, + trend: FLAT_TREND, ... - trend: flat, + trend: FLAT_TREND, ... - trend: flat, + trend: FLAT_TREND, ... - [stats, chartTrend, flat] + [stats, chartTrend] );Also applies to: 165-166
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/analytics/AnalyticsBentoGrid.tsx` around lines 87 - 89, The local constant flat (type TrendResult) is recreated each render which changes its identity and forces the useMemo for tiles to recompute; fix this by giving flat a stable identity—either hoist it to module scope as a top-level constant (e.g., FLAT: TrendResult) or memoize it inside the component with useMemo/useRef—then reference that stable flat when building tiles in the tiles useMemo so tiles only recomputes when its real dependencies change.
205-216: Replace class string interpolation/ternary with a class helper.Line 205 and Line 216 should use
clsx(or equivalent) rather than template/ternary class composition in JSX.♻️ Suggested refactor
+import clsx from 'clsx'; ... - <motion.div + <motion.div key={tile.label} variants={tileVariants} - className={`${tile.colSpan} ${tile.rowSpan}`} + className={clsx(tile.colSpan, tile.rowSpan)} > ... - <p - className={`font-bold tracking-tight ${tile.large ? 'text-4xl' : 'text-2xl'}`} - > + <p + className={clsx( + 'font-bold tracking-tight', + 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 205 - 216, Replace the template/ternary class strings with a class helper (clsx): import clsx at the top of the file, change the container className that uses `${tile.colSpan} ${tile.rowSpan}` to use clsx(tile.colSpan, tile.rowSpan), and change the paragraph className that uses `font-bold tracking-tight ${tile.large ? 'text-4xl' : 'text-2xl'}` to use clsx('font-bold tracking-tight', tile.large ? 'text-4xl' : 'text-2xl'); ensure the rest of the JSX (Card, CardHeader, CardContent, TrendBadge, tile.icon, tile.trend) is unaffected.components/analytics/AnalyticsChart.tsx (2)
40-57: Convert function declarations to typed const arrow functions.Both
CustomTooltipandAnalyticsChartshould follow the TS/TSX convention required in this repo.♻️ Suggested refactor
-function CustomTooltip({ +const CustomTooltip = ({ active, payload, label, -}: TooltipProps<number, string>) { +}: TooltipProps<number, string>): JSX.Element | null => { if (!active || !payload?.length) return null; return ( <div className='border-border bg-card rounded-xl border px-4 py-3 shadow-xl'> <p className='text-muted-foreground mb-1 text-xs'>{label}</p> <p className='text-sm font-semibold'> {payload[0].value}{' '} <span className='text-muted-foreground font-normal'>activities</span> </p> </div> ); -} +}; -export function AnalyticsChart({ chart }: Props) { +export const AnalyticsChart = ({ chart }: Props): JSX.Element => { const [range, setRange] = useState<Range>('30D'); const ranges: Range[] = ['7D', '30D', '90D', 'ALL']; ... -} +};As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".
Also applies to: 57-184
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/analytics/AnalyticsChart.tsx` around lines 40 - 57, Convert the function declarations to const arrow functions with explicit type annotations: replace "function CustomTooltip({...}: TooltipProps<number, string>)" with a const arrow like "const CustomTooltip: React.FC<TooltipProps<number, string>> = ({...}) => { ... }" (preserve the active/payload/label handling and return JSX), and replace "export function AnalyticsChart({ chart }: Props)" with "export const AnalyticsChart: React.FC<Props> = ({ chart }) => { ... }" (keep existing export, props usage, and component body). Ensure you import React types if needed and maintain existing names and behavior for CustomTooltip and AnalyticsChart so references elsewhere remain valid.
86-95: Use a namedhandle...click handler and a class helper for toggle buttons.Line 89 and Line 91 currently use inline state mutation plus ternary class composition in JSX. This violates project conventions and is harder to maintain.
♻️ Suggested refactor
+import clsx from 'clsx'; ... export function AnalyticsChart({ chart }: Props) { const [range, setRange] = useState<Range>('30D'); const ranges: Range[] = ['7D', '30D', '90D', 'ALL']; + const handleRangeChange = (nextRange: Range): void => { + setRange(nextRange); + }; ... {ranges.map(r => ( <button key={r} - onClick={() => setRange(r)} + onClick={() => handleRangeChange(r)} aria-pressed={range === r} - className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150 ${ - range === r - ? 'bg-primary text-primary-foreground shadow' - : 'text-muted-foreground hover:text-foreground' - }`} + className={clsx( + 'rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150', + range === r + ? 'bg-primary text-primary-foreground shadow' + : 'text-muted-foreground hover:text-foreground' + )} > {r} </button> ))}As per coding guidelines, "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX" and "Event handlers should start with 'handle' prefix (e.g., handleClick, handleSubmit)".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/analytics/AnalyticsChart.tsx` around lines 86 - 95, Extract the inline click handler and class ternary into named helpers: add a handler function (e.g., handleRangeClick or handleSelectRange) that calls setRange(r) and use it as the onClick in the ranges.map button, and replace the inline ternary className with a clsx (or classNames) helper expression that composes 'rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150' plus either 'bg-primary text-primary-foreground shadow' when range === r or 'text-muted-foreground hover:text-foreground' otherwise; ensure you import clsx (or your project's class helper) and keep aria-pressed={range === r} unchanged so behavior and accessibility remain the same.app/me/analytics/page.tsx (2)
104-113: Refactor filter buttons to use ahandle...handler and class helper.Line 107 and Line 109 currently use inline state updates and conditional class ternaries in JSX.
♻️ Suggested refactor
+import clsx from 'clsx'; ... function AnalyticsContent() { const { user, isLoading } = useAuthStatus(); const [activityFilter, setActivityFilter] = useState('All Time'); + const handleActivityFilterChange = (filter: string): void => { + setActivityFilter(filter); + }; ... {FILTER_OPTIONS.map(f => ( <button key={f} - onClick={() => setActivityFilter(f)} + onClick={() => handleActivityFilterChange(f)} aria-pressed={activityFilter === f} - className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150 ${ - activityFilter === f - ? 'bg-primary text-primary-foreground shadow' - : 'text-muted-foreground hover:text-foreground' - }`} + className={clsx( + 'rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150', + activityFilter === f + ? 'bg-primary text-primary-foreground shadow' + : 'text-muted-foreground hover:text-foreground' + )} > {f} </button> ))}As per coding guidelines, "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX" and "Event handlers should start with 'handle' prefix (e.g., handleClick, handleSubmit)".
🤖 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 104 - 113, Refactor the filter button JSX to remove the inline arrow and ternary class logic: create a handler named handleActivityFilterClick (or handleSetActivityFilter) that calls setActivityFilter(f) and use that in onClick instead of () => setActivityFilter(f); replace the conditional className string for the button with a clsx (or classNames) call to compose classes based on activityFilter === f; ensure you import clsx and update the button to reference the handler and the composed class helper while keeping the existing keys (FILTER_OPTIONS, activityFilter, setActivityFilter) intact.
30-37: Use typed const arrow components instead of function declarations.
AnalyticsContentandAnalyticsPageshould follow the project TSX component declaration convention.♻️ Suggested refactor
-function AnalyticsContent() { +const AnalyticsContent = (): JSX.Element => { ... -} +}; -export default function AnalyticsPage() { - return ( +const AnalyticsPage = (): JSX.Element => { + return ( <AuthGuard redirectTo='/auth?mode=signin' fallback={<div className='p-8 text-center'>Authenticating...</div>} > <AnalyticsContent /> </AuthGuard> ); -} +}; + +export default AnalyticsPage;As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".
Also applies to: 128-137
🤖 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 30 - 37, Convert the two function declarations into typed const arrow components: replace the function declaration for AnalyticsContent with a const declaration (e.g. const AnalyticsContent: React.FC or React.FunctionComponent = () => { ... }) and do the same for AnalyticsPage (the component referenced later around the other occurrence). Ensure you keep the existing props/types (use GetMeResponse usage) and any hooks/returns intact, add the appropriate React import if missing, and export the default component the same way as before.
🤖 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 104-113: Refactor the filter button JSX to remove the inline arrow
and ternary class logic: create a handler named handleActivityFilterClick (or
handleSetActivityFilter) that calls setActivityFilter(f) and use that in onClick
instead of () => setActivityFilter(f); replace the conditional className string
for the button with a clsx (or classNames) call to compose classes based on
activityFilter === f; ensure you import clsx and update the button to reference
the handler and the composed class helper while keeping the existing keys
(FILTER_OPTIONS, activityFilter, setActivityFilter) intact.
- Around line 30-37: Convert the two function declarations into typed const
arrow components: replace the function declaration for AnalyticsContent with a
const declaration (e.g. const AnalyticsContent: React.FC or
React.FunctionComponent = () => { ... }) and do the same for AnalyticsPage (the
component referenced later around the other occurrence). Ensure you keep the
existing props/types (use GetMeResponse usage) and any hooks/returns intact, add
the appropriate React import if missing, and export the default component the
same way as before.
In `@components/analytics/AnalyticsBentoGrid.tsx`:
- Around line 27-59: Change the component declarations from function
declarations to typed const arrow functions: replace the function TrendBadge({
trend }: { trend: TrendResult }) { ... } with a const TrendBadge: React.FC<{
trend: TrendResult }> = ({ trend }) => { ... } (and do the same for
AnalyticsBentoGrid, using an appropriate props interface/type and explicit
React.FC or React.FunctionComponent annotation). Ensure all React imports/types
remain available (e.g., React, TrendResult) and update any exported names to use
the const bindings so the components keep the same external API.
- Around line 87-89: The local constant flat (type TrendResult) is recreated
each render which changes its identity and forces the useMemo for tiles to
recompute; fix this by giving flat a stable identity—either hoist it to module
scope as a top-level constant (e.g., FLAT: TrendResult) or memoize it inside the
component with useMemo/useRef—then reference that stable flat when building
tiles in the tiles useMemo so tiles only recomputes when its real dependencies
change.
- Around line 205-216: Replace the template/ternary class strings with a class
helper (clsx): import clsx at the top of the file, change the container
className that uses `${tile.colSpan} ${tile.rowSpan}` to use clsx(tile.colSpan,
tile.rowSpan), and change the paragraph className that uses `font-bold
tracking-tight ${tile.large ? 'text-4xl' : 'text-2xl'}` to use clsx('font-bold
tracking-tight', tile.large ? 'text-4xl' : 'text-2xl'); ensure the rest of the
JSX (Card, CardHeader, CardContent, TrendBadge, tile.icon, tile.trend) is
unaffected.
In `@components/analytics/AnalyticsChart.tsx`:
- Around line 40-57: Convert the function declarations to const arrow functions
with explicit type annotations: replace "function CustomTooltip({...}:
TooltipProps<number, string>)" with a const arrow like "const CustomTooltip:
React.FC<TooltipProps<number, string>> = ({...}) => { ... }" (preserve the
active/payload/label handling and return JSX), and replace "export function
AnalyticsChart({ chart }: Props)" with "export const AnalyticsChart:
React.FC<Props> = ({ chart }) => { ... }" (keep existing export, props usage,
and component body). Ensure you import React types if needed and maintain
existing names and behavior for CustomTooltip and AnalyticsChart so references
elsewhere remain valid.
- Around line 86-95: Extract the inline click handler and class ternary into
named helpers: add a handler function (e.g., handleRangeClick or
handleSelectRange) that calls setRange(r) and use it as the onClick in the
ranges.map button, and replace the inline ternary className with a clsx (or
classNames) helper expression that composes 'rounded-lg px-3 py-1.5 text-xs
font-medium transition-all duration-150' plus either 'bg-primary
text-primary-foreground shadow' when range === r or 'text-muted-foreground
hover:text-foreground' otherwise; ensure you import clsx (or your project's
class helper) and keep aria-pressed={range === r} unchanged so behavior and
accessibility remain the same.
ℹ️ 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.
📒 Files selected for processing (3)
app/me/analytics/page.tsxcomponents/analytics/AnalyticsBentoGrid.tsxcomponents/analytics/AnalyticsChart.tsx
77558ed to
8690841
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (8)
lib/utils/calculateTrend.ts (1)
6-65: Apply typed const-arrow function style for exported TS utilities.The module uses function declarations for exported helpers; repo convention prefers typed const-arrow exports.
As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".
🤖 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 - 65, Convert the exported function declarations to typed const arrow functions: replace the function declarations for calculateTrend, calculateChartTrend, transformChartData, and filterChartByRange with exported consts using arrow functions and explicit return types (e.g., export const calculateTrend = (current: number | null | undefined, previous: number | null | undefined): TrendResult => { ... }), preserving the existing logic and signatures (including TrendResult and parameter types) and keep helper internals and defaulting behavior unchanged; ensure all export names remain identical so call sites are unaffected.components/analytics/AnalyticsBentoGrid.tsx (3)
27-59: Adopt typed const-arrow declarations for component functions in this TSX file.
TrendBadgeandAnalyticsBentoGridare function declarations; repository style asks for typed const-arrow declarations.As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".
Also applies to: 85-230
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/analytics/AnalyticsBentoGrid.tsx` around lines 27 - 59, The file uses function declarations for React components (e.g., TrendBadge and AnalyticsBentoGrid); convert them to typed const-arrow components with explicit React.FC (or appropriate prop) type annotations — replace "function TrendBadge({ trend }: { trend: TrendResult })" with a const declaration like "const TrendBadge: React.FC<{ trend: TrendResult }> = ({ trend }) => { ... }" (and do the same for AnalyticsBentoGrid and any other component functions in the range), ensuring you export/keep names and behaviors unchanged and update any internal references accordingly.
87-89: Stabilizeflattrend reference to avoid defeatinguseMemodependencies.Line 87 recreates
flaton every render; Line 165 then treats it as a dependency, forcingtilesrecomputation each render.♻️ Suggested refactor
+const FLAT_TREND: TrendResult = { percentage: 0, direction: 'flat' }; ... export function AnalyticsBentoGrid({ stats, chart }: Props) { const chartTrend = useMemo(() => calculateChartTrend(chart), [chart]); - const flat: TrendResult = { percentage: 0, direction: 'flat' }; const tiles: TileConfig[] = useMemo( () => [ ... - trend: flat, + trend: FLAT_TREND, ... ], - [stats, chartTrend, flat] + [stats, chartTrend] );Also applies to: 165-166
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/analytics/AnalyticsBentoGrid.tsx` around lines 87 - 89, The local const flat (TrendResult) is recreated each render, breaking memoization for tiles (useMemo depends on flat); stabilize its reference by moving it out of the render or memoizing it — e.g., define a module-level constant (FLAT: TrendResult = Object.freeze({ percentage: 0, direction: 'flat' })) or wrap flat in React.useMemo(() => ({ percentage:0, direction:'flat' }), []) and then use that stable identifier in the tiles useMemo dependency list (update usages at the symbols flat and tiles accordingly).
205-216: Useclsxfor conditional class composition in tile containers/text sizes.Lines 205 and 216 use template-string interpolation for conditional classes instead of a class helper.
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 205 - 216, The tile container and text-size class usages in AnalyticsBentoGrid currently use template strings for conditional classes; import clsx (or classNames) and replace the two spots: the outer element's className that currently interpolates `${tile.colSpan} ${tile.rowSpan}` should become className={clsx(tile.colSpan, tile.rowSpan)}, and the paragraph's className that uses `font-bold tracking-tight ${tile.large ? 'text-4xl' : 'text-2xl'}` should become className={clsx('font-bold tracking-tight', tile.large ? 'text-4xl' : 'text-2xl')}; ensure you add the clsx import at the top of the file.components/analytics/AnalyticsChart.tsx (2)
87-95: Refactor range buttons to usehandle*handler naming andclsxfor conditional classes.Line 89 uses an inline handler and Lines 91-95 use ternary string classes; this diverges from repo conventions and makes button styling logic harder to scale.
♻️ Suggested refactor
+import clsx from 'clsx'; ... export function AnalyticsChart({ chart }: Props) { const [range, setRange] = useState<Range>('30D'); const ranges: Range[] = ['7D', '30D', '90D', 'ALL']; + const handleRangeChange = (nextRange: Range): void => setRange(nextRange); ... {ranges.map(r => ( <button key={r} - onClick={() => setRange(r)} + type='button' + onClick={() => handleRangeChange(r)} aria-pressed={range === r} - className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150 ${ - range === r - ? 'bg-primary text-primary-foreground shadow' - : 'text-muted-foreground hover:text-foreground' - }`} + className={clsx( + 'rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150', + range === r + ? 'bg-primary text-primary-foreground shadow' + : 'text-muted-foreground hover:text-foreground' + )} > {r} </button> ))}As per coding guidelines, "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX" and "Event handlers should start with 'handle' prefix (e.g., handleClick, handleSubmit)".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/analytics/AnalyticsChart.tsx` around lines 87 - 95, The range buttons use an inline onClick and ternary string concatenation for className; refactor by extracting the click logic into a named handler (e.g., create a function handleRangeChange or handleRangeClick that calls setRange) and replace the inline onClick with that handler, and switch the className expression to use clsx (import clsx if not present) to conditionally apply 'bg-primary text-primary-foreground shadow' when range === r and the muted/hover classes otherwise; update any typings/props if needed and ensure aria-pressed still uses range === r.
40-57: Use typedconstarrow functions instead of function declarations in this TSX module.
CustomTooltipandAnalyticsChartare declared withfunction; repository convention asks for typed const-arrow declarations.♻️ Suggested refactor
-function CustomTooltip({ +const CustomTooltip = ({ active, payload, label, -}: TooltipProps<number, string>) { +}: TooltipProps<number, string>): JSX.Element | null => { if (!active || !payload?.length) return null; return ( <div className='border-border bg-card rounded-xl border px-4 py-3 shadow-xl'> ... </div> ); -} +}; -export function AnalyticsChart({ chart }: Props) { +export const AnalyticsChart = ({ chart }: Props): JSX.Element => { ... -} +};As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".
Also applies to: 57-184
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/analytics/AnalyticsChart.tsx` around lines 40 - 57, Replace the function declarations with typed const-arrow components: change CustomTooltip to a const arrow with an explicit type annotation (e.g., const CustomTooltip = ({ active, payload, label }: TooltipProps<number, string>): JSX.Element | null => { ... }) and change AnalyticsChart to an exported const arrow with Props typed (e.g., export const AnalyticsChart = ({ chart }: Props): JSX.Element => { ... }); preserve existing logic/JSX and imports, and apply the same refactor for other function declarations in this module (around the 57-184 range) to match the repository convention.app/me/analytics/page.tsx (2)
105-113: Refactor activity-filter buttons to usehandle*handlers andclsx.Line 107 uses inline click logic, and Lines 109-113 use ternary class composition; both conflict with repo conventions.
As per coding guidelines, "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX" and "Event handlers should start with 'handle' prefix (e.g., handleClick, handleSubmit)".
🤖 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 105 - 113, Replace the inline click handler and ternary className on the activity filter buttons with a named handler and clsx usage: create a handler named handleActivityFilterClick (or similar) that calls setActivityFilter(f) and use it as the button's onClick, and replace the template literal/ternary in className with clsx(...) combining the base classes and conditional classes using activityFilter === f; reference the existing setActivityFilter, activityFilter and the button render that uses key={f} and f to locate where to change.
30-126: Use typed const-arrow components forAnalyticsContentandAnalyticsPage.Both components are declared with
functionsyntax; repository standard prefers typed const-arrow declarations.As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".
Also applies to: 128-137
🤖 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 30 - 126, Convert the function declarations to typed const-arrow components: replace "function AnalyticsContent()" with "const AnalyticsContent: React.FC = () => { ... }" (keeping all existing hooks/useMemo/useState and return JSX intact), and replace the page export (currently "function AnalyticsPage" or default function) with "const AnalyticsPage: React.FC = () => { ... }" and export default AnalyticsPage; ensure you import React types if needed and keep component names AnalyticsContent and AnalyticsPage unchanged so references (AnalyticsBentoGrid, ActivityFeed, etc.) continue to work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/me/analytics/page.tsx`:
- Line 55: Replace the activities source with meData.recentActivities by
changing the activities declaration that currently uses meData.user?.activities
to use meData.recentActivities (cast to Activity[]), update the ActivityFeed
invocation to accept an explicit activities prop (pass activities into the
ActivityFeed component instead of relying on it to derive from
user.user.activities), and modify the ActivityFeed component signature/prop
usage to accept and use the incoming activities prop rather than pulling
activities from the nested user object.
In `@lib/utils/calculateTrend.ts`:
- Around line 30-35: The midpoint split in calculateTrend.ts currently slices
the incoming data array (variables mid, previous, current) without guaranteeing
chronological order, which can flip trend direction; before computing mid and
slicing, sort the data array in ascending order by its date/timestamp field
(e.g., data.sort((a,b) => new Date(a.date) - new Date(b.date)) or by numeric
timestamp) so that previous and current windows represent earlier vs later
periods, then compute mid, previous, current and call calculateTrend as before.
- Around line 61-64: The cutoff uses the current timestamp so entries on the
cutoff day can be excluded; modify the logic in calculateTrend (the cutoff
variable and the filtering for filtered) to normalize both cutoff and each data
date to day boundaries (e.g., setHours(0,0,0,0) or create UTC-midnight dates)
before comparing, i.e., compute cutoffStart = (new Date()).setHours(0,0,0,0) and
compare against each data date normalized to its start-of-day (normalize new
Date(d.date) similarly) so boundary-day entries are included correctly; keep the
same fallback (data.slice(-days)) behavior.
---
Nitpick comments:
In `@app/me/analytics/page.tsx`:
- Around line 105-113: Replace the inline click handler and ternary className on
the activity filter buttons with a named handler and clsx usage: create a
handler named handleActivityFilterClick (or similar) that calls
setActivityFilter(f) and use it as the button's onClick, and replace the
template literal/ternary in className with clsx(...) combining the base classes
and conditional classes using activityFilter === f; reference the existing
setActivityFilter, activityFilter and the button render that uses key={f} and f
to locate where to change.
- Around line 30-126: Convert the function declarations to typed const-arrow
components: replace "function AnalyticsContent()" with "const AnalyticsContent:
React.FC = () => { ... }" (keeping all existing hooks/useMemo/useState and
return JSX intact), and replace the page export (currently "function
AnalyticsPage" or default function) with "const AnalyticsPage: React.FC = () =>
{ ... }" and export default AnalyticsPage; ensure you import React types if
needed and keep component names AnalyticsContent and AnalyticsPage unchanged so
references (AnalyticsBentoGrid, ActivityFeed, etc.) continue to work.
In `@components/analytics/AnalyticsBentoGrid.tsx`:
- Around line 27-59: The file uses function declarations for React components
(e.g., TrendBadge and AnalyticsBentoGrid); convert them to typed const-arrow
components with explicit React.FC (or appropriate prop) type annotations —
replace "function TrendBadge({ trend }: { trend: TrendResult })" with a const
declaration like "const TrendBadge: React.FC<{ trend: TrendResult }> = ({ trend
}) => { ... }" (and do the same for AnalyticsBentoGrid and any other component
functions in the range), ensuring you export/keep names and behaviors unchanged
and update any internal references accordingly.
- Around line 87-89: The local const flat (TrendResult) is recreated each
render, breaking memoization for tiles (useMemo depends on flat); stabilize its
reference by moving it out of the render or memoizing it — e.g., define a
module-level constant (FLAT: TrendResult = Object.freeze({ percentage: 0,
direction: 'flat' })) or wrap flat in React.useMemo(() => ({ percentage:0,
direction:'flat' }), []) and then use that stable identifier in the tiles
useMemo dependency list (update usages at the symbols flat and tiles
accordingly).
- Around line 205-216: The tile container and text-size class usages in
AnalyticsBentoGrid currently use template strings for conditional classes;
import clsx (or classNames) and replace the two spots: the outer element's
className that currently interpolates `${tile.colSpan} ${tile.rowSpan}` should
become className={clsx(tile.colSpan, tile.rowSpan)}, and the paragraph's
className that uses `font-bold tracking-tight ${tile.large ? 'text-4xl' :
'text-2xl'}` should become className={clsx('font-bold tracking-tight',
tile.large ? 'text-4xl' : 'text-2xl')}; ensure you add the clsx import at the
top of the file.
In `@components/analytics/AnalyticsChart.tsx`:
- Around line 87-95: The range buttons use an inline onClick and ternary string
concatenation for className; refactor by extracting the click logic into a named
handler (e.g., create a function handleRangeChange or handleRangeClick that
calls setRange) and replace the inline onClick with that handler, and switch the
className expression to use clsx (import clsx if not present) to conditionally
apply 'bg-primary text-primary-foreground shadow' when range === r and the
muted/hover classes otherwise; update any typings/props if needed and ensure
aria-pressed still uses range === r.
- Around line 40-57: Replace the function declarations with typed const-arrow
components: change CustomTooltip to a const arrow with an explicit type
annotation (e.g., const CustomTooltip = ({ active, payload, label }:
TooltipProps<number, string>): JSX.Element | null => { ... }) and change
AnalyticsChart to an exported const arrow with Props typed (e.g., export const
AnalyticsChart = ({ chart }: Props): JSX.Element => { ... }); preserve existing
logic/JSX and imports, and apply the same refactor for other function
declarations in this module (around the 57-184 range) to match the repository
convention.
In `@lib/utils/calculateTrend.ts`:
- Around line 6-65: Convert the exported function declarations to typed const
arrow functions: replace the function declarations for calculateTrend,
calculateChartTrend, transformChartData, and filterChartByRange with exported
consts using arrow functions and explicit return types (e.g., export const
calculateTrend = (current: number | null | undefined, previous: number | null |
undefined): TrendResult => { ... }), preserving the existing logic and
signatures (including TrendResult and parameter types) and keep helper internals
and defaulting behavior unchanged; ensure all export names remain identical so
call sites are 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.
📒 Files selected for processing (4)
app/me/analytics/page.tsxcomponents/analytics/AnalyticsBentoGrid.tsxcomponents/analytics/AnalyticsChart.tsxlib/utils/calculateTrend.ts
| ); | ||
| } | ||
|
|
||
| const activities = (meData.user?.activities ?? []) as Activity[]; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify activity field shape and usage sites
set -euo pipefail
echo "== GetMeResponse / User shape =="
rg -n -C3 'interface GetMeResponse|interface User|recentActivities|activities' lib/api/types.ts
echo
echo "== Analytics page activity source =="
rg -n -C3 'meData\.user\?\.activities|recentActivities|ActivityHeatmap|ActivityFeed' app/me/analytics/page.tsx
echo
echo "== ActivityFeed expected input shape =="
rg -n -C3 'type ActivityFeedProps|user\.user\.activities|recentActivities' components/profile/ActivityFeed.tsxRepository: boundlessfi/boundless
Length of output: 3578
Use meData.recentActivities instead of meData.user?.activities to prevent silently empty activity widgets.
Line 55 sources activities from meData.user?.activities, but GetMeResponse explicitly provides recentActivities at the top level. If the backend populates only recentActivities and omits the nested user.activities field, both the heatmap and feed render empty despite valid analytics data in the response. The comment on line 77 ("uses recentActivities") also contradicts the actual code path.
Replace line 55 with:
const activities = (meData.recentActivities ?? []) as Activity[];
Also update line 121 to pass activities directly to ActivityFeed:
<ActivityFeed filter={activityFilter} activities={activities} />
Then verify ActivityFeed component accepts an activities prop instead of deriving it from user.user.activities.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/me/analytics/page.tsx` at line 55, Replace the activities source with
meData.recentActivities by changing the activities declaration that currently
uses meData.user?.activities to use meData.recentActivities (cast to
Activity[]), update the ActivityFeed invocation to accept an explicit activities
prop (pass activities into the ActivityFeed component instead of relying on it
to derive from user.user.activities), and modify the ActivityFeed component
signature/prop usage to accept and use the incoming activities prop rather than
pulling activities from the nested user object.
| const mid = Math.floor(data.length / 2); | ||
| const previous = data | ||
| .slice(0, mid) | ||
| .reduce((sum, d) => sum + (d.count ?? 0), 0); | ||
| const current = data.slice(mid).reduce((sum, d) => sum + (d.count ?? 0), 0); | ||
| return calculateTrend(current, previous); |
There was a problem hiding this comment.
Sort chart data before midpoint split to avoid incorrect trend direction/percentage.
Line 30 currently splits by array position. If API order is not strictly ascending by date, previous vs current windows become wrong.
🐛 Suggested fix
export function calculateChartTrend(
data: Array<{ date: string; count: number }> | null | undefined
): TrendResult {
if (!data || data.length === 0) return { percentage: 0, direction: 'flat' };
- const mid = Math.floor(data.length / 2);
- const previous = data
+ const sorted = [...data].sort(
+ (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
+ );
+ const mid = Math.floor(sorted.length / 2);
+ const previous = sorted
.slice(0, mid)
.reduce((sum, d) => sum + (d.count ?? 0), 0);
- const current = data.slice(mid).reduce((sum, d) => sum + (d.count ?? 0), 0);
+ const current = sorted
+ .slice(mid)
+ .reduce((sum, d) => sum + (d.count ?? 0), 0);
return calculateTrend(current, previous);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/utils/calculateTrend.ts` around lines 30 - 35, The midpoint split in
calculateTrend.ts currently slices the incoming data array (variables mid,
previous, current) without guaranteeing chronological order, which can flip
trend direction; before computing mid and slicing, sort the data array in
ascending order by its date/timestamp field (e.g., data.sort((a,b) => new
Date(a.date) - new Date(b.date)) or by numeric timestamp) so that previous and
current windows represent earlier vs later periods, then compute mid, previous,
current and call calculateTrend as before.
| const cutoff = new Date(); | ||
| cutoff.setDate(cutoff.getDate() - days); | ||
| const filtered = data.filter(d => new Date(d.date) >= cutoff); | ||
| return filtered.length > 0 ? filtered : data.slice(-days); |
There was a problem hiding this comment.
Normalize cutoff/data dates to day boundaries for range filtering.
Line 61 uses the current timestamp (hour/minute included), so boundary-day entries can be excluded depending on timezone/time-of-day.
🐛 Suggested fix
if (days === 'ALL' || data.length === 0) return data;
const cutoff = new Date();
+ cutoff.setHours(0, 0, 0, 0);
cutoff.setDate(cutoff.getDate() - days);
- const filtered = data.filter(d => new Date(d.date) >= cutoff);
+ const filtered = data.filter(d => {
+ const day = new Date(d.date);
+ day.setHours(0, 0, 0, 0);
+ return day >= cutoff;
+ });
return filtered.length > 0 ? filtered : data.slice(-days);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const cutoff = new Date(); | |
| cutoff.setDate(cutoff.getDate() - days); | |
| const filtered = data.filter(d => new Date(d.date) >= cutoff); | |
| return filtered.length > 0 ? filtered : data.slice(-days); | |
| const cutoff = new Date(); | |
| cutoff.setHours(0, 0, 0, 0); | |
| cutoff.setDate(cutoff.getDate() - days); | |
| const filtered = data.filter(d => { | |
| const day = new Date(d.date); | |
| day.setHours(0, 0, 0, 0); | |
| return day >= cutoff; | |
| }); | |
| return filtered.length > 0 ? filtered : data.slice(-days); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/utils/calculateTrend.ts` around lines 61 - 64, The cutoff uses the
current timestamp so entries on the cutoff day can be excluded; modify the logic
in calculateTrend (the cutoff variable and the filtering for filtered) to
normalize both cutoff and each data date to day boundaries (e.g.,
setHours(0,0,0,0) or create UTC-midnight dates) before comparing, i.e., compute
cutoffStart = (new Date()).setHours(0,0,0,0) and compare against each data date
normalized to its start-of-day (normalize new Date(d.date) similarly) so
boundary-day entries are included correctly; keep the same fallback
(data.slice(-days)) behavior.
|
Gm @Benjtalkshow trust you are doing great today? After testing thoroughly on staging, here’s what I found:
This looks like a backend/staging data issue and not related to this PR. The frontend is correctly displaying whatever the API returns. I’ll drop the video proof shortly. |
feat(analytics): implement
/me/analyticspersonal growth dashboard — closes #397Overview
This PR implements the personal analytics dashboard at
app/me/analyticsas 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 byAuthGuard, reads fromuser.profilepopulated by the existinggetMe()call inuseAuthStatuscomponents/analytics/AnalyticsBentoGrid.tsx— Asymmetric bento-style grid with staggered Framer Motion mount animation, surfacing all stats fromGetMeResponse.statswith trend badgescomponents/analytics/AnalyticsChart.tsx— RechartsLineChartofGetMeResponse.chartwith 7D / 30D / 90D / ALL range toggle, custom tooltip, and cyan→indigo gradient linelib/utils/calculateTrend.ts— Pure utility functions for trend percentage calculation, chart data transformation, and range filtering. Independently testable with no React dependenciesHow It Works
All data flows from
user.profilewhich is set byuseAuthStatus()via the already-existinggetMe()fetch inhooks/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
calculateTrendutility 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()andfilterChartByRange()in the utility file — not inline in JSX.Design
col-span-2 row-span-2hero tile, with supporting metrics in smaller tiles around it#06b6d4) to Deep Indigo (#4f46e5) gradient applied to the chart line and active range toggle button, consistent with the Boundless palettebg-white/[0.03],border-white/[0.06],backdrop-blur-smAccessibility
<table>with proper column headers for screen readerssr-onlydata table fallback listing every date and activity count in the active rangearia-pressedandrole="group"with a descriptive labelaria-labeldescribing direction and percentage valueA Note on Chart Data
GetMeResponse.chartexposes a single{ date, count }activity time series. The issue referenced a "Projects vs Earnings" multi-line chart, however noearningsfield 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,AnalyticsChartis structured to accommodate extra<Line>entries with minimal changes.Testing Checklist
npm run lintpasses with no errorsnpm run buildpasses with no breaking changesScreenshots
Desktop view
Mobile view
Summary by CodeRabbit