Skip to content

Commit 4d6a7df

Browse files
committed
checkpoint
1 parent 0cc6d27 commit 4d6a7df

File tree

4 files changed

+142
-17
lines changed

4 files changed

+142
-17
lines changed

src/components/Spinner.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { twMerge } from 'tailwind-merge'
2-
import { Fan } from 'lucide-react'
2+
import { Loader2 } from 'lucide-react'
33

44
interface SpinnerProps {
55
className?: string
66
}
77

88
export function Spinner({ className }: SpinnerProps) {
99
return (
10-
<Fan
10+
<Loader2
1111
className={twMerge(
1212
'animate-spin text-gray-900 dark:text-white text-2xl',
1313
className,

src/routes/admin/index.tsx

Lines changed: 73 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -540,23 +540,45 @@ function ActivityTab({
540540
<div className="text-sm font-medium text-gray-600 dark:text-gray-400 mb-2">
541541
WAU (7 days)
542542
</div>
543-
<div className="text-4xl font-bold text-gray-900 dark:text-white mb-2">
544-
{dauStats.wau}
543+
<div className="flex items-center gap-3 mb-2">
544+
<div className="text-4xl font-bold text-gray-900 dark:text-white">
545+
{dauStats.wau}
546+
</div>
547+
{dauStats.wauPrevious > 0 && (
548+
<ChangeIndicator
549+
value={
550+
((dauStats.wau - dauStats.wauPrevious) /
551+
dauStats.wauPrevious) *
552+
100
553+
}
554+
/>
555+
)}
545556
</div>
546557
<div className="text-xs text-gray-500 dark:text-gray-400">
547-
Weekly active users
558+
vs previous 7 days ({dauStats.wauPrevious})
548559
</div>
549560
</Card>
550561

551562
<Card className="p-6">
552563
<div className="text-sm font-medium text-gray-600 dark:text-gray-400 mb-2">
553564
MAU (30 days)
554565
</div>
555-
<div className="text-4xl font-bold text-gray-900 dark:text-white mb-2">
556-
{dauStats.mau}
566+
<div className="flex items-center gap-3 mb-2">
567+
<div className="text-4xl font-bold text-gray-900 dark:text-white">
568+
{dauStats.mau}
569+
</div>
570+
{dauStats.mauPrevious > 0 && (
571+
<ChangeIndicator
572+
value={
573+
((dauStats.mau - dauStats.mauPrevious) /
574+
dauStats.mauPrevious) *
575+
100
576+
}
577+
/>
578+
)}
557579
</div>
558580
<div className="text-xs text-gray-500 dark:text-gray-400">
559-
Monthly active users
581+
vs previous 30 days ({dauStats.mauPrevious})
560582
</div>
561583
</Card>
562584

@@ -608,35 +630,71 @@ function ActivityTab({
608630
<div className="text-sm font-medium text-gray-600 dark:text-gray-400 mb-2">
609631
Active Users Today
610632
</div>
611-
<div className="text-4xl font-bold text-gray-900 dark:text-white mb-2">
612-
{activityStats.activeUsers.today}
633+
<div className="flex items-center gap-3 mb-2">
634+
<div className="text-4xl font-bold text-gray-900 dark:text-white">
635+
{activityStats.activeUsers.today}
636+
</div>
637+
{activityStats.activeUsers.yesterday > 0 && (
638+
<ChangeIndicator
639+
value={
640+
((activityStats.activeUsers.today -
641+
activityStats.activeUsers.yesterday) /
642+
activityStats.activeUsers.yesterday) *
643+
100
644+
}
645+
/>
646+
)}
613647
</div>
614648
<div className="text-xs text-gray-500 dark:text-gray-400">
615-
Unique users who logged in
649+
vs yesterday ({activityStats.activeUsers.yesterday})
616650
</div>
617651
</Card>
618652

619653
<Card className="p-6">
620654
<div className="text-sm font-medium text-gray-600 dark:text-gray-400 mb-2">
621655
Active Users (7d)
622656
</div>
623-
<div className="text-4xl font-bold text-gray-900 dark:text-white mb-2">
624-
{activityStats.activeUsers.last7Days}
657+
<div className="flex items-center gap-3 mb-2">
658+
<div className="text-4xl font-bold text-gray-900 dark:text-white">
659+
{activityStats.activeUsers.last7Days}
660+
</div>
661+
{activityStats.activeUsers.previous7Days > 0 && (
662+
<ChangeIndicator
663+
value={
664+
((activityStats.activeUsers.last7Days -
665+
activityStats.activeUsers.previous7Days) /
666+
activityStats.activeUsers.previous7Days) *
667+
100
668+
}
669+
/>
670+
)}
625671
</div>
626672
<div className="text-xs text-gray-500 dark:text-gray-400">
627-
Last 7 days
673+
vs previous 7 days ({activityStats.activeUsers.previous7Days})
628674
</div>
629675
</Card>
630676

631677
<Card className="p-6">
632678
<div className="text-sm font-medium text-gray-600 dark:text-gray-400 mb-2">
633679
Active Users (30d)
634680
</div>
635-
<div className="text-4xl font-bold text-gray-900 dark:text-white mb-2">
636-
{activityStats.activeUsers.last30Days}
681+
<div className="flex items-center gap-3 mb-2">
682+
<div className="text-4xl font-bold text-gray-900 dark:text-white">
683+
{activityStats.activeUsers.last30Days}
684+
</div>
685+
{activityStats.activeUsers.previous30Days > 0 && (
686+
<ChangeIndicator
687+
value={
688+
((activityStats.activeUsers.last30Days -
689+
activityStats.activeUsers.previous30Days) /
690+
activityStats.activeUsers.previous30Days) *
691+
100
692+
}
693+
/>
694+
)}
637695
</div>
638696
<div className="text-xs text-gray-500 dark:text-gray-400">
639-
Last 30 days
697+
vs previous 30 days ({activityStats.activeUsers.previous30Days})
640698
</div>
641699
</Card>
642700
</div>

src/utils/activity.server.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ export async function getActiveUserStats(): Promise<{
107107
wau: number
108108
mau: number
109109
dauYesterday: number
110+
wauPrevious: number
111+
mauPrevious: number
110112
}> {
111113
const today = getTodayDate()
112114
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000)
@@ -115,9 +117,15 @@ export async function getActiveUserStats(): Promise<{
115117
const last7Days = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
116118
.toISOString()
117119
.split('T')[0]
120+
const last14Days = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000)
121+
.toISOString()
122+
.split('T')[0]
118123
const last30Days = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
119124
.toISOString()
120125
.split('T')[0]
126+
const last60Days = new Date(Date.now() - 60 * 24 * 60 * 60 * 1000)
127+
.toISOString()
128+
.split('T')[0]
121129

122130
const [dauResult] = await db
123131
.select({ count: sql<number>`count(distinct ${userActivity.userId})` })
@@ -134,16 +142,40 @@ export async function getActiveUserStats(): Promise<{
134142
.from(userActivity)
135143
.where(gte(userActivity.date, last7Days))
136144

145+
// Previous 7 days (day 8-14)
146+
const [wauPreviousResult] = await db
147+
.select({ count: sql<number>`count(distinct ${userActivity.userId})` })
148+
.from(userActivity)
149+
.where(
150+
and(
151+
gte(userActivity.date, last14Days),
152+
sql`${userActivity.date} < ${last7Days}`,
153+
),
154+
)
155+
137156
const [mauResult] = await db
138157
.select({ count: sql<number>`count(distinct ${userActivity.userId})` })
139158
.from(userActivity)
140159
.where(gte(userActivity.date, last30Days))
141160

161+
// Previous 30 days (day 31-60)
162+
const [mauPreviousResult] = await db
163+
.select({ count: sql<number>`count(distinct ${userActivity.userId})` })
164+
.from(userActivity)
165+
.where(
166+
and(
167+
gte(userActivity.date, last60Days),
168+
sql`${userActivity.date} < ${last30Days}`,
169+
),
170+
)
171+
142172
return {
143173
dau: Number(dauResult.count),
144174
dauYesterday: Number(dauYesterdayResult.count),
145175
wau: Number(wauResult.count),
176+
wauPrevious: Number(wauPreviousResult.count),
146177
mau: Number(mauResult.count),
178+
mauPrevious: Number(mauPreviousResult.count),
147179
}
148180
}
149181

src/utils/audit.functions.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,16 +276,48 @@ export const getActivityStats = createServerFn({ method: 'POST' }).handler(
276276
.from(loginHistory)
277277
.where(gte(loginHistory.createdAt, today))
278278

279+
const [uniqueActiveYesterday] = await db
280+
.select({ count: sql<number>`count(distinct ${loginHistory.userId})` })
281+
.from(loginHistory)
282+
.where(
283+
and(
284+
gte(loginHistory.createdAt, yesterday),
285+
lte(loginHistory.createdAt, today),
286+
),
287+
)
288+
279289
const [uniqueActiveLast7Days] = await db
280290
.select({ count: sql<number>`count(distinct ${loginHistory.userId})` })
281291
.from(loginHistory)
282292
.where(gte(loginHistory.createdAt, last7Days))
283293

294+
const last14Days = new Date(today.getTime() - 14 * 24 * 60 * 60 * 1000)
295+
const [uniqueActivePrevious7Days] = await db
296+
.select({ count: sql<number>`count(distinct ${loginHistory.userId})` })
297+
.from(loginHistory)
298+
.where(
299+
and(
300+
gte(loginHistory.createdAt, last14Days),
301+
lte(loginHistory.createdAt, last7Days),
302+
),
303+
)
304+
284305
const [uniqueActiveLast30Days] = await db
285306
.select({ count: sql<number>`count(distinct ${loginHistory.userId})` })
286307
.from(loginHistory)
287308
.where(gte(loginHistory.createdAt, last30Days))
288309

310+
const last60Days = new Date(today.getTime() - 60 * 24 * 60 * 60 * 1000)
311+
const [uniqueActivePrevious30Days] = await db
312+
.select({ count: sql<number>`count(distinct ${loginHistory.userId})` })
313+
.from(loginHistory)
314+
.where(
315+
and(
316+
gte(loginHistory.createdAt, last60Days),
317+
lte(loginHistory.createdAt, last30Days),
318+
),
319+
)
320+
289321
// Provider breakdown
290322
const providerStats = await db
291323
.select({
@@ -353,8 +385,11 @@ export const getActivityStats = createServerFn({ method: 'POST' }).handler(
353385
},
354386
activeUsers: {
355387
today: Number(uniqueActiveToday.count),
388+
yesterday: Number(uniqueActiveYesterday.count),
356389
last7Days: Number(uniqueActiveLast7Days.count),
390+
previous7Days: Number(uniqueActivePrevious7Days.count),
357391
last30Days: Number(uniqueActiveLast30Days.count),
392+
previous30Days: Number(uniqueActivePrevious30Days.count),
358393
},
359394
providerBreakdown: providerStats.reduce(
360395
(acc, { provider, count }) => {

0 commit comments

Comments
 (0)