1+ 'use server' ;
2+ import { prisma } from '@repo/prisma' ;
3+ import { requireAuth } from './auth' ;
4+
5+ interface DashboardMetrics {
6+ totalEntries : number ;
7+ writingStreak : number ;
8+ responseRate : number ;
9+ recentActivity : number ;
10+ }
11+
12+ interface EntryPreview {
13+ id : string ;
14+ content : string ;
15+ createdAt : Date ;
16+ promptId : string ;
17+ prompt : {
18+ content : string ;
19+ } ;
20+ }
21+
22+ interface DashboardData {
23+ metrics : DashboardMetrics ;
24+ recentEntries : EntryPreview [ ] ;
25+ isEmpty : boolean ;
26+ }
27+
28+ /**
29+ * Calculate writing streak based on user frequency and entry dates
30+ */
31+ async function calculateWritingStreak ( userId : string ) : Promise < number > {
32+ const user = await prisma . user . findUnique ( {
33+ where : { id : userId } ,
34+ select : { frequency : true }
35+ } ) ;
36+
37+ const entries = await prisma . entry . findMany ( {
38+ where : { userId } ,
39+ orderBy : { createdAt : 'desc' } ,
40+ select : { createdAt : true }
41+ } ) ;
42+
43+ if ( ! entries . length || ! user ) return 0 ;
44+
45+ const intervalDays = user . frequency === 'daily' ? 1 : 7 ;
46+ let streak = 0 ;
47+ let currentDate = new Date ( ) ;
48+
49+ for ( const entry of entries ) {
50+ const daysDiff = Math . floor (
51+ ( currentDate . getTime ( ) - entry . createdAt . getTime ( ) ) / ( 1000 * 60 * 60 * 24 )
52+ ) ;
53+
54+ if ( daysDiff <= intervalDays * ( streak + 1 ) ) {
55+ streak ++ ;
56+ currentDate = entry . createdAt ;
57+ } else {
58+ break ;
59+ }
60+ }
61+
62+ return streak ;
63+ }
64+
65+ /**
66+ * Calculate response rate over last 30 days
67+ */
68+ async function calculateResponseRate ( userId : string ) : Promise < number > {
69+ const thirtyDaysAgo = new Date ( Date . now ( ) - 30 * 24 * 60 * 60 * 1000 ) ;
70+
71+ const [ totalPrompts , totalEntries ] = await Promise . all ( [
72+ prisma . prompt . count ( {
73+ where : {
74+ userId,
75+ createdAt : { gte : thirtyDaysAgo }
76+ }
77+ } ) ,
78+ prisma . entry . count ( {
79+ where : {
80+ userId,
81+ createdAt : { gte : thirtyDaysAgo }
82+ }
83+ } )
84+ ] ) ;
85+
86+ return totalPrompts > 0 ? Math . round ( ( totalEntries / totalPrompts ) * 100 ) : 0 ;
87+ }
88+
89+ /**
90+ * Fetch all dashboard data for authenticated user
91+ */
92+ export async function fetchDashboardData ( ) : Promise < DashboardData > {
93+ const authResult = await requireAuth ( ) ;
94+
95+ // requireAuth redirects if invalid, so if we reach here, userId exists
96+ const userId = authResult . userId ! ;
97+
98+ const [
99+ totalEntries ,
100+ recentEntries ,
101+ writingStreak ,
102+ responseRate ,
103+ recentActivity
104+ ] = await Promise . all ( [
105+ // Total entries count
106+ prisma . entry . count ( { where : { userId } } ) ,
107+
108+ // Last 5 entries with prompt context
109+ prisma . entry . findMany ( {
110+ where : { userId } ,
111+ orderBy : { createdAt : 'desc' } ,
112+ take : 5 ,
113+ include : {
114+ prompt : {
115+ select : { content : true }
116+ }
117+ }
118+ } ) ,
119+
120+ // Writing streak calculation
121+ calculateWritingStreak ( userId ) ,
122+
123+ // Response rate over 30 days
124+ calculateResponseRate ( userId ) ,
125+
126+ // Recent activity (entries in last 30 days)
127+ prisma . entry . count ( {
128+ where : {
129+ userId,
130+ createdAt : {
131+ gte : new Date ( Date . now ( ) - 30 * 24 * 60 * 60 * 1000 )
132+ }
133+ }
134+ } )
135+ ] ) ;
136+
137+ return {
138+ metrics : {
139+ totalEntries,
140+ writingStreak,
141+ responseRate,
142+ recentActivity
143+ } ,
144+ recentEntries,
145+ isEmpty : totalEntries === 0
146+ } ;
147+ }
148+
149+ /**
150+ * Create entry snippet for previews
151+ */
152+ export async function createEntrySnippet ( content : string , maxLength : number = 150 ) : Promise < string > {
153+ if ( content . length <= maxLength ) return content ;
154+
155+ const truncated = content . substring ( 0 , maxLength ) ;
156+ const lastSpace = truncated . lastIndexOf ( ' ' ) ;
157+
158+ return lastSpace > 0
159+ ? truncated . substring ( 0 , lastSpace ) + '...'
160+ : truncated + '...' ;
161+ }
0 commit comments