Skip to content

Commit efc08e6

Browse files
ralyodioqwencoder
andcommitted
fix: add auth-aware navbar and fix usage page auth check
- Wrap entire app in AuthProvider in root layout so SiteHeader can show logged-in state (username dropdown with Account, Usage, Sign Out) instead of always showing Log In / Sign Up - Convert usage page from server-side cookie auth to client-side AuthContext check (matching the app's localStorage token pattern) - Remove redundant AuthProvider wrappers from account page Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
1 parent 1f27f1e commit efc08e6

5 files changed

Lines changed: 152 additions & 74 deletions

File tree

src/app/account/page.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
"use client";
22

3-
import { AuthProvider } from "@/lib/auth-context";
43
import AccountContent from "./account-content";
54

65
export default function AccountPage() {
7-
return (
8-
<AuthProvider>
9-
<AccountContent />
10-
</AuthProvider>
11-
);
6+
return <AccountContent />;
127
}

src/app/layout.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
22
import "./globals.css";
33
import SiteHeader from "@/components/SiteHeader";
44
import SiteFooter from "@/components/SiteFooter";
5+
import { AuthProvider } from "@/lib/auth-context";
56

67
export const metadata: Metadata = {
78
title: "ThreatCrush — Real-Time Threat Intelligence Platform",
@@ -79,9 +80,11 @@ export default function RootLayout({
7980
/>
8081
</head>
8182
<body className="antialiased">
82-
<SiteHeader />
83-
{children}
84-
<SiteFooter />
83+
<AuthProvider>
84+
<SiteHeader />
85+
{children}
86+
<SiteFooter />
87+
</AuthProvider>
8588
</body>
8689
</html>
8790
);

src/app/usage/page.tsx

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,16 @@
11
import type { Metadata } from "next";
2-
import { redirect } from "next/navigation";
3-
import { getSupabaseAdmin } from "@/lib/supabase";
2+
import { AuthProvider } from "@/lib/auth-context";
43
import UsageContent from "./usage-content";
54

65
export const metadata: Metadata = {
76
title: "Usage & Billing — ThreatCrush",
87
description: "Monitor your AI usage credits, spending, and billing for ThreatCrush modules.",
98
};
109

11-
async function requireUsageAccess() {
12-
const supabase = getSupabaseAdmin();
13-
14-
// Read the Supabase auth token cookie directly.
15-
// This is intentionally simple for now and mirrors the app's current auth shape.
16-
const { cookies } = await import("next/headers");
17-
const cookieStore = await cookies();
18-
19-
const possibleCookies = [
20-
cookieStore.get("sb-access-token")?.value,
21-
cookieStore.get("supabase-auth-token")?.value,
22-
].filter(Boolean) as string[];
23-
24-
for (const token of possibleCookies) {
25-
const { data } = await supabase.auth.getUser(token);
26-
if (data?.user) {
27-
return data.user;
28-
}
29-
}
30-
31-
redirect("/auth/login?next=/usage");
32-
}
33-
34-
export default async function UsagePage() {
35-
await requireUsageAccess();
36-
return <UsageContent />;
10+
export default function UsagePage() {
11+
return (
12+
<AuthProvider>
13+
<UsageContent />
14+
</AuthProvider>
15+
);
3716
}

src/app/usage/usage-content.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import { useState, useEffect } from "react";
44
import Link from "next/link";
5+
import { useRouter } from "next/navigation";
6+
import { useAuth } from "@/lib/auth-context";
57
import ScrollReveal from "@/components/ScrollReveal";
68

79
interface UsageData {
@@ -56,20 +58,28 @@ function formatTime(iso: string): string {
5658
}
5759

5860
export default function UsageContent() {
61+
const { signedIn, loading: authLoading } = useAuth();
62+
const router = useRouter();
5963
const [data, setData] = useState<UsageData | null>(null);
6064
const [loading, setLoading] = useState(true);
6165
const [topupAmount, setTopupAmount] = useState<number>(10);
6266
const [showTopup, setShowTopup] = useState(false);
6367

6468
useEffect(() => {
65-
fetch("/api/usage")
66-
.then((r) => r.json())
67-
.then((d) => {
68-
setData(d);
69-
setLoading(false);
70-
})
71-
.catch(() => setLoading(false));
72-
}, []);
69+
if (!authLoading && !signedIn) {
70+
window.location.href = "/auth/login?next=/usage";
71+
return;
72+
}
73+
if (!authLoading && signedIn) {
74+
fetch("/api/usage")
75+
.then((r) => r.json())
76+
.then((d) => {
77+
setData(d);
78+
setLoading(false);
79+
})
80+
.catch(() => setLoading(false));
81+
}
82+
}, [signedIn, authLoading]);
7383

7484
const handleTopup = async () => {
7585
if (!topupAmount || topupAmount < 5) return;

src/components/SiteHeader.tsx

Lines changed: 120 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { useState, useEffect } from "react";
4+
import { useAuth } from "@/lib/auth-context";
45
import WaitlistModal from "@/components/WaitlistModal";
56

67
export default function SiteHeader() {
8+
const { signedIn, profile, signOut, loading } = useAuth();
79
const [modalOpen, setModalOpen] = useState(false);
810
const [mobileNavOpen, setMobileNavOpen] = useState(false);
11+
const [userDropdownOpen, setUserDropdownOpen] = useState(false);
912
const openModal = () => setModalOpen(true);
1013

14+
// Close dropdown on outside click
15+
useEffect(() => {
16+
if (!userDropdownOpen) return;
17+
const handler = () => setUserDropdownOpen(false);
18+
document.addEventListener("click", handler);
19+
return () => document.removeEventListener("click", handler);
20+
}, [userDropdownOpen]);
21+
22+
const handleSignOut = async () => {
23+
await signOut();
24+
window.location.href = "/";
25+
};
26+
1127
return (
1228
<>
1329
<WaitlistModal open={modalOpen} onClose={() => setModalOpen(false)} />
@@ -38,12 +54,73 @@ export default function SiteHeader() {
3854
</a>
3955
</div>
4056
<div className="flex items-center gap-2 sm:gap-3">
41-
<a
42-
href="/auth/signup"
43-
className="rounded-lg bg-tc-green px-3 py-2 text-sm font-bold text-black transition-all hover:bg-tc-green-dim sm:px-4"
44-
>
45-
Sign Up
46-
</a>
57+
{!loading && (
58+
signedIn ? (
59+
<div className="flex items-center gap-2 relative">
60+
<button
61+
type="button"
62+
onClick={(e) => {
63+
e.stopPropagation();
64+
setUserDropdownOpen((v) => !v);
65+
}}
66+
className="rounded-lg bg-tc-green/10 border border-tc-green/30 px-3 py-2 text-sm font-bold text-tc-green transition-all hover:bg-tc-green/20 flex items-center gap-2"
67+
>
68+
<span>{profile?.display_name || profile?.email || "Account"}</span>
69+
<span className="text-xs">{userDropdownOpen ? "▲" : "▼"}</span>
70+
</button>
71+
{userDropdownOpen && (
72+
<div
73+
className="absolute right-0 top-full mt-2 w-56 bg-tc-card border border-tc-border rounded-xl shadow-xl overflow-hidden z-50"
74+
onClick={(e) => e.stopPropagation()}
75+
>
76+
<div className="px-4 py-3 border-b border-tc-border">
77+
<p className="text-sm text-white font-medium truncate">{profile?.display_name || "User"}</p>
78+
<p className="text-xs text-tc-text-dim truncate">{profile?.email}</p>
79+
</div>
80+
<div className="py-1">
81+
<a
82+
href="/account"
83+
className="block px-4 py-2 text-sm text-tc-text-dim hover:text-white hover:bg-tc-darker transition-colors"
84+
onClick={() => setUserDropdownOpen(false)}
85+
>
86+
Account Settings
87+
</a>
88+
<a
89+
href="/usage"
90+
className="block px-4 py-2 text-sm text-tc-text-dim hover:text-white hover:bg-tc-darker transition-colors"
91+
onClick={() => setUserDropdownOpen(false)}
92+
>
93+
Usage & Billing
94+
</a>
95+
</div>
96+
<div className="border-t border-tc-border">
97+
<button
98+
onClick={handleSignOut}
99+
className="w-full text-left px-4 py-2 text-sm text-red-400 hover:bg-red-500/10 transition-colors"
100+
>
101+
Sign Out
102+
</button>
103+
</div>
104+
</div>
105+
)}
106+
</div>
107+
) : (
108+
<>
109+
<a
110+
href="/auth/login"
111+
className="hidden lg:inline text-sm text-tc-text-dim hover:text-tc-green transition-colors"
112+
>
113+
Log In
114+
</a>
115+
<a
116+
href="/auth/signup"
117+
className="rounded-lg bg-tc-green px-3 py-2 text-sm font-bold text-black transition-all hover:bg-tc-green-dim sm:px-4"
118+
>
119+
Sign Up
120+
</a>
121+
</>
122+
)
123+
)}
47124
<button
48125
type="button"
49126
onClick={() => setMobileNavOpen((open) => !open)}
@@ -54,18 +131,14 @@ export default function SiteHeader() {
54131
<span className="text-lg">{mobileNavOpen ? "✕" : "☰"}</span>
55132
</button>
56133
<div className="hidden lg:flex items-center gap-3">
57-
<a
58-
href="/auth/login"
59-
className="text-sm text-tc-text-dim hover:text-tc-green transition-colors"
60-
>
61-
Log In
62-
</a>
63-
<button
64-
onClick={openModal}
65-
className="rounded-lg border border-tc-green/30 px-4 py-2 text-sm font-bold text-tc-green transition-all hover:bg-tc-green/10"
66-
>
67-
Join Waitlist
68-
</button>
134+
{!signedIn && !loading && (
135+
<button
136+
onClick={openModal}
137+
className="rounded-lg border border-tc-green/30 px-4 py-2 text-sm font-bold text-tc-green transition-all hover:bg-tc-green/10"
138+
>
139+
Join Waitlist
140+
</button>
141+
)}
69142
</div>
70143
</div>
71144
</div>
@@ -79,16 +152,34 @@ export default function SiteHeader() {
79152
<a href="/#pricing" className="hover:text-tc-green transition-colors" onClick={() => setMobileNavOpen(false)}>Pricing</a>
80153
<a href="/investors" className="hover:text-tc-green transition-colors" onClick={() => setMobileNavOpen(false)}>Investors</a>
81154
<a href="/#faq" className="hover:text-tc-green transition-colors" onClick={() => setMobileNavOpen(false)}>FAQ</a>
82-
<a href="/auth/login" className="hover:text-tc-green transition-colors" onClick={() => setMobileNavOpen(false)}>Log In</a>
83-
<button
84-
onClick={() => {
85-
setMobileNavOpen(false);
86-
openModal();
87-
}}
88-
className="rounded-lg border border-tc-green/30 px-4 py-2 text-left font-bold text-tc-green transition-all hover:bg-tc-green/10"
89-
>
90-
Join Waitlist
91-
</button>
155+
{signedIn ? (
156+
<>
157+
<a href="/account" className="text-tc-green transition-colors" onClick={() => setMobileNavOpen(false)}>Account</a>
158+
<button
159+
onClick={async () => {
160+
setMobileNavOpen(false);
161+
await signOut();
162+
window.location.href = "/";
163+
}}
164+
className="text-left text-red-400 hover:text-red-300 transition-colors"
165+
>
166+
Sign Out
167+
</button>
168+
</>
169+
) : (
170+
<>
171+
<a href="/auth/login" className="hover:text-tc-green transition-colors" onClick={() => setMobileNavOpen(false)}>Log In</a>
172+
<button
173+
onClick={() => {
174+
setMobileNavOpen(false);
175+
openModal();
176+
}}
177+
className="rounded-lg border border-tc-green/30 px-4 py-2 text-left font-bold text-tc-green transition-all hover:bg-tc-green/10"
178+
>
179+
Join Waitlist
180+
</button>
181+
</>
182+
)}
92183
<a
93184
href="https://github.com/profullstack/threatcrush"
94185
target="_blank"

0 commit comments

Comments
 (0)