@@ -5,137 +5,149 @@ import Link from "next/link";
55import { useSession } from "next-auth/react" ;
66
77export default function UserDashboard ( { user : userProp = null , reports : reportsProp = [ ] } ) {
8- const { data : session } = useSession ( ) ;
9- const [ user , setUser ] = useState ( userProp ) ;
10- const [ reports , setReports ] = useState ( reportsProp ) ;
11- const [ loading , setLoading ] = useState ( ! userProp ) ;
8+ const { data : session } = useSession ( ) ;
9+ const [ user , setUser ] = useState ( userProp ) ;
10+ const [ reports , setReports ] = useState ( reportsProp ) ;
11+ const [ loading , setLoading ] = useState ( ! userProp ) ;
1212
13- // Optional: try to fetch richer user data if an API exists (safe fallback if 404)
14- useEffect ( ( ) => {
15- if ( userProp ) return ;
16- let abort = false ;
17- async function load ( ) {
18- try {
19- const res = await fetch ( "/api/user/me" , { cache : "no-store" } ) ;
20- if ( ! res . ok ) throw new Error ( "no api" ) ;
21- const data = await res . json ( ) ;
22- if ( ! abort ) {
23- setUser ( data ?. user || null ) ;
24- setReports ( data ?. reports || [ ] ) ;
13+ // Optional: try to fetch richer user data if an API exists (safe fallback if 404)
14+ useEffect ( ( ) => {
15+ if ( userProp ) return ;
16+ let abort = false ;
17+ async function load ( ) {
18+ try {
19+ const res = await fetch ( "/api/user/me" , { cache : "no-store" } ) ;
20+ if ( ! res . ok ) throw new Error ( "no api" ) ;
21+ const data = await res . json ( ) ;
22+ if ( ! abort ) {
23+ setUser ( data ?. user || null ) ;
24+ setReports ( data ?. reports || [ ] ) ;
25+ }
26+ } catch {
27+ // Fallback to session info only
28+ if ( ! abort ) {
29+ setUser ( {
30+ fullName : session ?. user ?. name || "User" ,
31+ email : session ?. user ?. email || "" ,
32+ scansCount : 0 ,
33+ lastLoginAt : null ,
34+ } ) ;
35+ }
36+ } finally {
37+ if ( ! abort ) setLoading ( false ) ;
38+ }
2539 }
26- } catch {
27- // Fallback to session info only
28- if ( ! abort ) {
29- setUser ( {
30- fullName : session ?. user ?. name || "User" ,
31- email : session ?. user ?. email || "" ,
32- scansCount : 0 ,
33- lastLoginAt : null ,
34- } ) ;
40+ load ( ) ;
41+ return ( ) => { abort = true ; } ;
42+ } , [ session , userProp ] ) ;
43+
44+ const stats = useMemo ( ( ) => {
45+ const totalScans = user ?. scansCount ?? 0 ;
46+ const lastLogin = user ?. lastLoginAt
47+ ? new Date ( user . lastLoginAt ) . toLocaleString ( )
48+ : "—" ;
49+ console . log ( "User Stats:" , { totalScans, lastLogin } ) ;
50+
51+ // Prefer latestScan (when populated with createdAt), else fallback to reports[0]
52+ let lastReport = "—" ;
53+ if (
54+ user ?. latestScan &&
55+ typeof user . latestScan === "object" &&
56+ user . latestScan . createdAt
57+ ) {
58+ lastReport = new Date ( user . latestScan . createdAt ) . toLocaleString ( ) ;
59+ } else if ( reports ?. length ) {
60+ lastReport = new Date ( reports [ 0 ] ?. createdAt ) . toLocaleString ( ) ;
3561 }
36- } finally {
37- if ( ! abort ) setLoading ( false ) ;
38- }
39- }
40- load ( ) ;
41- return ( ) => { abort = true ; } ;
42- } , [ session , userProp ] ) ;
4362
44- const stats = useMemo ( ( ) => {
45- return [
46- { label : "Total Scans" , value : user ?. scansCount ?? 0 } ,
47- {
48- label : "Last Login" ,
49- value : user ?. lastLoginAt ? new Date ( user . lastLoginAt ) . toLocaleString ( ) : "—" ,
50- } ,
51- {
52- label : "Last Report" ,
53- value : reports ?. length ? new Date ( reports [ 0 ] ?. createdAt ) . toLocaleString ( ) : "—" ,
54- } ,
55- ] ;
56- } , [ user , reports ] ) ;
63+ return [
64+ { label : "Total Scans" , value : totalScans } ,
65+ { label : "Last Login" , value : lastLogin } ,
66+ { label : "Last Report" , value : lastReport } ,
67+ ] ;
68+ } , [ user , reports ] ) ;
5769
58- return (
59- < section className = "min-h-[calc(100vh-4rem )] bg-gradient-to-b from-white to-gray-50" >
60- < div className = "max-w-7xl mx-auto px-6 py-10" >
61- { /* Header */ }
62- < div className = "flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-8" >
63- < div >
64- < h1 className = "font-poppins text-2xl md:text-3xl font-bold text-[#00483a]" >
65- Welcome{ user ?. fullName ? `, ${ user . fullName } ` : "" } 👋
66- </ h1 >
67- < p className = "font-roboto text-gray-600" >
68- View your accessibility scans and reports.
69- </ p >
70- </ div >
71- < Link
72- href = "/scanner"
73- className = "inline-flex items-center justify-center gap-2 rounded-md bg-[#00d4ff] text-white font-semibold hover:bg-[#00d4ff]/90 shadow-md transition-all h-10 px-4"
74- >
75- Start New Scan
76- </ Link >
77- </ div >
70+ return (
71+ < section className = "mt-16 min-h-[calc(100vh-1rem )] bg-gradient-to-b from-white to-gray-50" >
72+ < div className = "max-w-7xl mx-auto px-6 py-10" >
73+ { /* Header */ }
74+ < div className = "flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-8" >
75+ < div >
76+ < h1 className = "font-poppins text-2xl md:text-3xl font-bold text-[#00483a]" >
77+ Welcome{ user ?. fullName ? `, ${ user . fullName } ` : "" } 👋
78+ </ h1 >
79+ < p className = "font-roboto text-gray-600" >
80+ View your accessibility scans and reports.
81+ </ p >
82+ </ div >
83+ < Link
84+ href = "/scanner"
85+ className = "inline-flex items-center justify-center gap-2 rounded-md bg-[#00d4ff] text-white font-semibold hover:bg-[#00d4ff]/90 shadow-md transition-all h-10 px-4"
86+ >
87+ Start New Scan
88+ </ Link >
89+ </ div >
7890
79- { /* Stats */ }
80- < div className = "grid sm:grid-cols-3 gap-4 mb-10" >
81- { stats . map ( ( s , i ) => (
82- < div
83- key = { i }
84- className = "rounded-xl bg-white shadow-card hover:shadow-elegant transition-all p-5"
85- >
86- < div className = "text-sm text-gray-500" > { s . label } </ div >
87- < div className = "mt-1 font-poppins text-xl text-gray-900" > { s . value } </ div >
88- </ div >
89- ) ) }
90- </ div >
91+ { /* Stats */ }
92+ < div className = "grid sm:grid-cols-3 gap-4 mb-10" >
93+ { stats . map ( ( s , i ) => (
94+ < div
95+ key = { i }
96+ className = "rounded-xl bg-white shadow-card hover:shadow-elegant transition-all p-5"
97+ >
98+ < div className = "text-sm text-gray-500" > { s . label } </ div >
99+ < div className = "mt-1 font-poppins text-xl text-gray-900" > { s . value } </ div >
100+ </ div >
101+ ) ) }
102+ </ div >
91103
92- { /* Reports */ }
93- < div className = "bg-white rounded-xl shadow-card p-6" >
94- < div className = "flex items-center justify-between mb-4" >
95- < h2 className = "font-poppins text-lg font-semibold text-gray-900" > Recent Reports</ h2 >
96- < Link href = "/dashboard/reports" className = "text-sm text-[#00d4ff] hover:underline" >
97- View all
98- </ Link >
99- </ div >
104+ { /* Reports */ }
105+ < div className = "bg-white rounded-xl shadow-card p-6" >
106+ < div className = "flex items-center justify-between mb-4" >
107+ < h2 className = "font-poppins text-lg font-semibold text-gray-900" > Recent Reports</ h2 >
108+ < Link href = "/dashboard/reports" className = "text-sm text-[#00d4ff] hover:underline" >
109+ View all
110+ </ Link >
111+ </ div >
100112
101- { loading ? (
102- < div className = "grid gap-3" >
103- { [ ...Array ( 3 ) ] . map ( ( _ , i ) => (
104- < div key = { i } className = "h-16 rounded-md bg-gray-100 animate-pulse" />
105- ) ) }
106- </ div >
107- ) : reports ?. length ? (
108- < ul className = "divide-y divide-gray-100" >
109- { reports . slice ( 0 , 5 ) . map ( ( r ) => (
110- < li key = { r . _id } className = "py-4 flex items-center justify-between" >
111- < div className = "min-w-0" >
112- < p className = "font-medium text-gray-900 truncate" > { r . targetUrl } </ p >
113- < p className = "text-sm text-gray-500" >
114- { new Date ( r . createdAt ) . toLocaleString ( ) } • { r . summary ?. issuesTotal ?? 0 } issues
115- </ p >
116- </ div >
117- < Link
118- href = { `/dashboard/reports/${ r . _id } ` }
119- className = "text-sm text-[#00d4ff] hover:underline"
120- >
121- Open
122- </ Link >
123- </ li >
124- ) ) }
125- </ ul >
126- ) : (
127- < div className = "text-center py-10" >
128- < p className = "font-roboto text-gray-600" > No reports yet.</ p >
129- < Link
130- href = "/scanner"
131- className = "mt-4 inline-flex items-center justify-center gap-2 rounded-md bg-[#00d4ff] text-white font-semibold hover:bg-[#00d4ff]/90 shadow-md transition-all h-10 px-4"
132- >
133- Run your first scan
134- </ Link >
113+ { loading ? (
114+ < div className = "grid gap-3" >
115+ { [ ...Array ( 3 ) ] . map ( ( _ , i ) => (
116+ < div key = { i } className = "h-16 rounded-md bg-gray-100 animate-pulse" />
117+ ) ) }
118+ </ div >
119+ ) : reports ?. length ? (
120+ < ul className = "divide-y divide-gray-100" >
121+ { reports . slice ( 0 , 5 ) . map ( ( r ) => (
122+ < li key = { r . _id } className = "py-4 flex items-center justify-between" >
123+ < div className = "min-w-0" >
124+ < p className = "font-medium text-gray-900 truncate" > { r . targetUrl } </ p >
125+ < p className = "text-sm text-gray-500" >
126+ { new Date ( r . createdAt ) . toLocaleString ( ) } • { r . summary ?. issuesTotal ?? 0 } issues
127+ </ p >
128+ </ div >
129+ < Link
130+ href = { `/dashboard/reports/${ r . _id } ` }
131+ className = "text-sm text-[#00d4ff] hover:underline"
132+ >
133+ Open
134+ </ Link >
135+ </ li >
136+ ) ) }
137+ </ ul >
138+ ) : (
139+ < div className = "text-center py-10" >
140+ < p className = "font-roboto text-gray-600" > No reports yet.</ p >
141+ < Link
142+ href = "/scanner"
143+ className = "mt-4 inline-flex items-center justify-center gap-2 rounded-md bg-[#00d4ff] text-white font-semibold hover:bg-[#00d4ff]/90 shadow-md transition-all h-10 px-4"
144+ >
145+ Run your first scan
146+ </ Link >
147+ </ div >
148+ ) }
149+ </ div >
135150 </ div >
136- ) }
137- </ div >
138- </ div >
139- </ section >
140- ) ;
151+ </ section >
152+ ) ;
141153}
0 commit comments