Skip to content

Commit 07b06b3

Browse files
committed
fix(analytics): address PR review corrections - use Card component, primary color, and integrate activity feeds
1 parent aa52724 commit 07b06b3

3 files changed

Lines changed: 237 additions & 157 deletions

File tree

app/me/analytics/page.tsx

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,35 @@
11
'use client';
22

3-
import { useMemo } from 'react';
3+
import { useMemo, useState } from 'react';
4+
import { motion } from 'framer-motion';
45
import { useAuthStatus } from '@/hooks/use-auth';
56
import { GetMeResponse } from '@/lib/api/types';
7+
import { Activity } from '@/types/user';
68
import { AnalyticsBentoGrid } from '@/components/analytics/AnalyticsBentoGrid';
79
import { AnalyticsChart } from '@/components/analytics/AnalyticsChart';
810
import { AuthGuard } from '@/components/auth';
911
import LoadingSpinner from '@/components/LoadingSpinner';
12+
import ActivityHeatmap from '@/components/profile/ActivityHeatMap';
13+
import ActivityFeed from '@/components/profile/ActivityFeed';
14+
import {
15+
Card,
16+
CardContent,
17+
CardDescription,
18+
CardHeader,
19+
CardTitle,
20+
} from '@/components/ui/card';
21+
22+
const FILTER_OPTIONS = [
23+
'All Time',
24+
'This Year',
25+
'This Month',
26+
'This Week',
27+
'Today',
28+
];
1029

1130
function AnalyticsContent() {
1231
const { user, isLoading } = useAuthStatus();
32+
const [activityFilter, setActivityFilter] = useState('All Time');
1333

1434
const meData = useMemo(
1535
() => (user?.profile as GetMeResponse | undefined) ?? null,
@@ -26,27 +46,81 @@ function AnalyticsContent() {
2646

2747
if (!meData?.stats || !meData?.chart) {
2848
return (
29-
<div className='flex h-64 items-center justify-center text-sm text-zinc-500'>
49+
<div className='text-muted-foreground flex h-64 items-center justify-center text-sm'>
3050
Analytics data unavailable.
3151
</div>
3252
);
3353
}
3454

55+
const activities = (meData.user?.activities ?? []) as Activity[];
56+
3557
return (
36-
<div className='flex flex-col gap-6 px-4 py-6 lg:px-6'>
37-
{/* Page header */}
38-
<div>
39-
<h1 className='text-2xl font-bold text-white'>Analytics</h1>
40-
<p className='mt-1 text-sm text-zinc-500'>
41-
Your personal growth dashboard
58+
<div className='container mx-auto space-y-8 px-6 py-8'>
59+
{/* Page header — matches earnings page pattern */}
60+
<motion.div
61+
initial={{ opacity: 0, y: 20 }}
62+
animate={{ opacity: 1, y: 0 }}
63+
className='flex flex-col gap-2'
64+
>
65+
<h1 className='text-3xl font-bold tracking-tight'>Analytics</h1>
66+
<p className='text-muted-foreground text-lg'>
67+
Your personal growth dashboard across the platform.
4268
</p>
43-
</div>
69+
</motion.div>
4470

45-
{/* Bento grid — core stats */}
71+
{/* Bento stats grid */}
4672
<AnalyticsBentoGrid stats={meData.stats} chart={meData.chart} />
4773

4874
{/* Activity chart */}
4975
<AnalyticsChart chart={meData.chart} />
76+
77+
{/* Activity heatmap — uses recentActivities as activity array */}
78+
<Card>
79+
<CardHeader>
80+
<CardTitle>Contribution Graph</CardTitle>
81+
<CardDescription>Your activity over the last year</CardDescription>
82+
</CardHeader>
83+
<CardContent>
84+
<ActivityHeatmap activities={activities} />
85+
</CardContent>
86+
</Card>
87+
88+
{/* Recent activity feed */}
89+
<Card>
90+
<CardHeader className='flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between'>
91+
<div>
92+
<CardTitle>Recent Activity</CardTitle>
93+
<CardDescription>
94+
What you have been up to on the platform
95+
</CardDescription>
96+
</div>
97+
98+
{/* Filter toggle — matches the pattern ActivityFeed expects */}
99+
<div
100+
role='group'
101+
aria-label='Activity time filter'
102+
className='border-border bg-muted flex flex-wrap gap-1 rounded-xl border p-1'
103+
>
104+
{FILTER_OPTIONS.map(f => (
105+
<button
106+
key={f}
107+
onClick={() => setActivityFilter(f)}
108+
aria-pressed={activityFilter === f}
109+
className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150 ${
110+
activityFilter === f
111+
? 'bg-primary text-primary-foreground shadow'
112+
: 'text-muted-foreground hover:text-foreground'
113+
}`}
114+
>
115+
{f}
116+
</button>
117+
))}
118+
</div>
119+
</CardHeader>
120+
<CardContent>
121+
<ActivityFeed filter={activityFilter} user={meData} />
122+
</CardContent>
123+
</Card>
50124
</div>
51125
);
52126
}

components/analytics/AnalyticsBentoGrid.tsx

Lines changed: 37 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import { useMemo } from 'react';
4-
import { motion } from 'framer-motion';
4+
import { motion, Variants } from 'framer-motion';
55
import {
66
TrendingUp,
77
TrendingDown,
@@ -15,6 +15,7 @@ import {
1515
ThumbsUp,
1616
GitBranch,
1717
} from 'lucide-react';
18+
import { Card, CardContent, CardHeader } from '@/components/ui/card';
1819
import { GetMeResponse } from '@/lib/api/types';
1920
import { calculateChartTrend, TrendResult } from '@/lib/utils/calculateTrend';
2021

@@ -28,7 +29,7 @@ function TrendBadge({ trend }: { trend: TrendResult }) {
2829
return (
2930
<span
3031
aria-label={`Up ${trend.percentage}%`}
31-
className='inline-flex items-center gap-1 rounded-full bg-emerald-500/15 px-2 py-0.5 text-xs font-medium text-emerald-400'
32+
className='bg-primary/15 text-primary inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium'
3233
>
3334
<TrendingUp className='h-3 w-3' aria-hidden='true' />
3435
{trend.percentage}%
@@ -49,7 +50,7 @@ function TrendBadge({ trend }: { trend: TrendResult }) {
4950
return (
5051
<span
5152
aria-label='No change'
52-
className='inline-flex items-center gap-1 rounded-full bg-zinc-700/50 px-2 py-0.5 text-xs font-medium text-zinc-400'
53+
className='bg-muted text-muted-foreground inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium'
5354
>
5455
<Minus className='h-3 w-3' aria-hidden='true' />
5556
0%
@@ -64,28 +65,26 @@ interface TileConfig {
6465
trend: TrendResult;
6566
colSpan: string;
6667
rowSpan: string;
67-
gradient: string;
6868
large?: boolean;
6969
}
7070

71-
const FLAT_TREND: TrendResult = { percentage: 0, direction: 'flat' };
72-
73-
const containerVariants = {
71+
const containerVariants: Variants = {
7472
hidden: {},
7573
show: { transition: { staggerChildren: 0.07 } },
7674
};
7775

78-
const tileVariants = {
76+
const tileVariants: Variants = {
7977
hidden: { opacity: 0, y: 16 },
8078
show: {
8179
opacity: 1,
8280
y: 0,
83-
transition: { duration: 0.35, ease: 'easeOut' as const },
81+
transition: { duration: 0.35, ease: [0.4, 0, 0.2, 1] as const },
8482
},
8583
};
8684

8785
export function AnalyticsBentoGrid({ stats, chart }: Props) {
8886
const chartTrend = useMemo(() => calculateChartTrend(chart), [chart]);
87+
const flat: TrendResult = { percentage: 0, direction: 'flat' };
8988

9089
const tiles: TileConfig[] = useMemo(
9190
() => [
@@ -96,8 +95,6 @@ export function AnalyticsBentoGrid({ stats, chart }: Props) {
9695
trend: chartTrend,
9796
colSpan: 'col-span-2',
9897
rowSpan: 'row-span-2',
99-
gradient:
100-
'bg-gradient-to-br from-[#06b6d4]/20 via-[#4f46e5]/10 to-transparent',
10198
large: true,
10299
},
103100
{
@@ -107,77 +104,70 @@ export function AnalyticsBentoGrid({ stats, chart }: Props) {
107104
trend: chartTrend,
108105
colSpan: 'col-span-1',
109106
rowSpan: 'row-span-1',
110-
gradient: 'bg-white/[0.03]',
111107
},
112108
{
113109
label: 'Projects Created',
114110
value: stats.projectsCreated,
115111
icon: <FolderGit2 className='h-4 w-4' />,
116-
trend: FLAT_TREND,
112+
trend: flat,
117113
colSpan: 'col-span-1',
118114
rowSpan: 'row-span-1',
119-
gradient: 'bg-white/[0.03]',
120115
},
121116
{
122117
label: 'Hackathons Entered',
123118
value: stats.hackathons,
124119
icon: <Trophy className='h-4 w-4' />,
125-
trend: FLAT_TREND,
120+
trend: flat,
126121
colSpan: 'col-span-1',
127122
rowSpan: 'row-span-1',
128-
gradient: 'bg-gradient-to-br from-amber-500/10 to-transparent',
129123
},
130124
{
131125
label: 'Followers',
132126
value: stats.followers,
133127
icon: <Users className='h-4 w-4' />,
134-
trend: FLAT_TREND,
128+
trend: flat,
135129
colSpan: 'col-span-1',
136130
rowSpan: 'row-span-1',
137-
gradient: 'bg-white/[0.03]',
138131
},
139132
{
140133
label: 'Total Contributed',
141134
value: stats.totalContributed,
142135
icon: <DollarSign className='h-4 w-4' />,
143-
trend: FLAT_TREND,
136+
trend: flat,
144137
colSpan: 'col-span-1',
145138
rowSpan: 'row-span-1',
146-
gradient: 'bg-gradient-to-br from-emerald-500/10 to-transparent',
147139
},
148140
{
149141
label: 'Comments Posted',
150142
value: stats.commentsPosted,
151143
icon: <MessageSquare className='h-4 w-4' />,
152-
trend: FLAT_TREND,
144+
trend: flat,
153145
colSpan: 'col-span-1',
154146
rowSpan: 'row-span-1',
155-
gradient: 'bg-white/[0.03]',
156147
},
157148
{
158149
label: 'Votes Cast',
159150
value: stats.votes,
160151
icon: <ThumbsUp className='h-4 w-4' />,
161-
trend: FLAT_TREND,
152+
trend: flat,
162153
colSpan: 'col-span-1',
163154
rowSpan: 'row-span-1',
164-
gradient: 'bg-white/[0.03]',
165155
},
166156
{
167157
label: 'Following',
168158
value: stats.following,
169159
icon: <GitBranch className='h-4 w-4' />,
170-
trend: FLAT_TREND,
160+
trend: flat,
171161
colSpan: 'col-span-1',
172162
rowSpan: 'row-span-1',
173-
gradient: 'bg-white/[0.03]',
174163
},
175164
],
176-
[stats, chartTrend]
165+
[stats, chartTrend, flat]
177166
);
178167

179168
return (
180169
<>
170+
{/* Screen reader fallback table */}
181171
<table className='sr-only' aria-label='Analytics statistics'>
182172
<thead>
183173
<tr>
@@ -206,29 +196,32 @@ export function AnalyticsBentoGrid({ stats, chart }: Props) {
206196
variants={containerVariants}
207197
initial='hidden'
208198
animate='show'
209-
className='grid grid-cols-2 gap-3 sm:grid-cols-4'
199+
className='grid grid-cols-2 gap-4 sm:grid-cols-4'
210200
>
211201
{tiles.map(tile => (
212202
<motion.div
213203
key={tile.label}
214204
variants={tileVariants}
215-
className={`relative flex flex-col justify-between rounded-2xl border border-white/[0.06] p-5 backdrop-blur-sm transition-colors duration-200 hover:border-white/10 ${tile.gradient} ${tile.colSpan} ${tile.rowSpan}`}
205+
className={`${tile.colSpan} ${tile.rowSpan}`}
216206
>
217-
<div className='pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-br from-white/[0.04] to-transparent' />
218-
<div className='flex items-start justify-between'>
219-
<div className='flex h-9 w-9 items-center justify-center rounded-xl bg-white/[0.06] text-zinc-300'>
220-
{tile.icon}
221-
</div>
222-
<TrendBadge trend={tile.trend} />
223-
</div>
224-
<div className='mt-4'>
225-
<p
226-
className={`font-bold tracking-tight text-white ${tile.large ? 'text-4xl' : 'text-2xl'}`}
227-
>
228-
{tile.value.toLocaleString()}
229-
</p>
230-
<p className='mt-1 text-sm text-zinc-500'>{tile.label}</p>
231-
</div>
207+
<Card className='h-full'>
208+
<CardHeader className='flex flex-row items-start justify-between space-y-0 pb-2'>
209+
<div className='bg-primary/10 text-primary flex h-9 w-9 items-center justify-center rounded-xl'>
210+
{tile.icon}
211+
</div>
212+
<TrendBadge trend={tile.trend} />
213+
</CardHeader>
214+
<CardContent>
215+
<p
216+
className={`font-bold tracking-tight ${tile.large ? 'text-4xl' : 'text-2xl'}`}
217+
>
218+
{tile.value.toLocaleString()}
219+
</p>
220+
<p className='text-muted-foreground mt-1 text-sm'>
221+
{tile.label}
222+
</p>
223+
</CardContent>
224+
</Card>
232225
</motion.div>
233226
))}
234227
</motion.div>

0 commit comments

Comments
 (0)