Skip to content

Commit 9b1eff7

Browse files
committed
Few Auth Updates
1 parent 6d3b150 commit 9b1eff7

6 files changed

Lines changed: 261 additions & 208 deletions

File tree

app/api/user/me/route.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { getServerSession } from "next-auth";
2+
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
3+
import connectDB from "@/db/connectDB.mjs";
4+
import User from "@/models/user";
5+
6+
export async function GET() {
7+
const session = await getServerSession(authOptions);
8+
if (!session?.user?.email) {
9+
return new Response(JSON.stringify({ error: "unauthorized" }), { status: 401 });
10+
}
11+
12+
await connectDB();
13+
const user = await User.findOne({ email: session.user.email })
14+
// .populate("latestScan", "createdAt")
15+
// .lean();
16+
17+
return new Response(JSON.stringify({ user }), {
18+
status: 200,
19+
headers: { "content-type": "application/json" },
20+
});
21+
}

app/dashboard/page.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
"use client"
2-
import Login from "@/components/login";
3-
import { useSession, signIn, signOut } from "next-auth/react"
4-
import { useRouter } from 'next/navigation'
2+
import { useEffect } from "react";
3+
import { useSession } from "next-auth/react";
4+
import { useRouter } from "next/navigation";
55
import UserDashboard from "@/components/dashboard";
66

77
export default function Home() {
8+
const { data: session, status } = useSession();
9+
const router = useRouter();
10+
11+
useEffect(() => {
12+
if (status === "authenticated") {
13+
router.push("/dashboard");
14+
}
15+
if (status === "unauthenticated") {
16+
router.push("/login");
17+
}
18+
}, [status, router]);
19+
20+
// if (status === "authenticated") return null; // prevent flicker
21+
822
return (
923
<div className="font-sans min-h-screen">
1024
<UserDashboard />
1125
</div>
1226
);
13-
}
27+
}

app/login/page.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
"use client"
2+
import { useEffect } from "react";
3+
import { useSession } from "next-auth/react";
4+
import { useRouter } from "next/navigation";
25
import Login from "@/components/login";
3-
import { useSession, signIn, signOut } from "next-auth/react"
4-
import { useRouter } from 'next/navigation'
56

67
export default function Home() {
7-
const { data: session } = useSession()
8-
if (session) {
9-
const router = useRouter()
10-
router.push('/dashboard')
11-
}
8+
const { data: session, status } = useSession();
9+
const router = useRouter();
10+
11+
useEffect(() => {
12+
if (status === "authenticated") {
13+
router.push("/dashboard");
14+
}
15+
}, [status, router]);
16+
17+
if (status === "authenticated") return null; // prevent flicker
1218

1319
return (
1420
<div className="font-sans min-h-screen">
1521
<Login />
1622
</div>
1723
);
18-
}
24+
}

components/dashboard.jsx

Lines changed: 136 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -5,137 +5,149 @@ import Link from "next/link";
55
import { useSession } from "next-auth/react";
66

77
export 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

Comments
 (0)