Skip to content

Commit 60d759b

Browse files
author
Miriad
committed
Merge branch 'feat/dashboard-auth' into dev
2 parents 87dc318 + e995ee0 commit 60d759b

File tree

9 files changed

+82
-42
lines changed

9 files changed

+82
-42
lines changed

app/(dashboard)/layout.tsx

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,26 @@ export default async function DashboardLayout({
3333
}: {
3434
children: React.ReactNode;
3535
}) {
36+
// Try to get user — if Supabase isn't configured or user isn't logged in,
37+
// the proxy will have already redirected to login for protected routes.
38+
// The login page itself renders without the sidebar chrome.
39+
let user = null;
40+
try {
41+
const supabaseUrl =
42+
process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL;
43+
const supabaseAnonKey =
44+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY;
45+
46+
if (supabaseUrl && supabaseAnonKey) {
47+
const { createClient } = await import("@/lib/supabase/server");
48+
const supabase = await createClient();
49+
const { data } = await supabase.auth.getUser();
50+
user = data?.user ?? null;
51+
}
52+
} catch {
53+
// Supabase not available — continue without user
54+
}
55+
3656
return (
3757
<html lang="en" suppressHydrationWarning>
3858
<body
@@ -48,15 +68,19 @@ export default async function DashboardLayout({
4868
enableSystem
4969
disableTransitionOnChange
5070
>
51-
<SidebarProvider>
52-
<AppSidebar />
53-
<SidebarInset>
54-
<SiteHeader />
55-
<main className="flex flex-1 flex-col gap-4 p-4 md:p-6">
56-
{children}
57-
</main>
58-
</SidebarInset>
59-
</SidebarProvider>
71+
{user ? (
72+
<SidebarProvider>
73+
<AppSidebar user={user} />
74+
<SidebarInset>
75+
<SiteHeader />
76+
<main className="flex flex-1 flex-col gap-4 p-4 md:p-6">
77+
{children}
78+
</main>
79+
</SidebarInset>
80+
</SidebarProvider>
81+
) : (
82+
<>{children}</>
83+
)}
6084
<Toaster />
6185
</ThemeProvider>
6286
</body>

app/api/dashboard/activity/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ export const dynamic = "force-dynamic";
77

88
export async function GET() {
99
const hasSupabase =
10-
process.env.NEXT_PUBLIC_SUPABASE_URL &&
11-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
10+
(process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL) &&
11+
(process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY);
1212

1313
if (!hasSupabase) {
1414
return NextResponse.json({ error: "Auth not configured" }, { status: 503 });

app/api/dashboard/metrics/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ export const dynamic = "force-dynamic";
77

88
export async function GET() {
99
const hasSupabase =
10-
process.env.NEXT_PUBLIC_SUPABASE_URL &&
11-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
10+
(process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL) &&
11+
(process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY);
1212

1313
if (!hasSupabase) {
1414
return NextResponse.json({ error: "Auth not configured" }, { status: 503 });

app/api/dashboard/pipeline/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ export const dynamic = "force-dynamic";
66

77
export async function GET() {
88
const hasSupabase =
9-
process.env.NEXT_PUBLIC_SUPABASE_URL &&
10-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
9+
(process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL) &&
10+
(process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY);
1111

1212
if (!hasSupabase) {
1313
return NextResponse.json({ error: "Auth not configured" }, { status: 503 });

app/api/dashboard/settings/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ const DEFAULT_SETTINGS = {
2323

2424
async function requireAuth() {
2525
const hasSupabase =
26-
process.env.NEXT_PUBLIC_SUPABASE_URL &&
27-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
26+
(process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL) &&
27+
(process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY);
2828

2929
if (!hasSupabase) {
3030
return { error: NextResponse.json({ error: "Auth not configured" }, { status: 503 }) };

components/nav-user.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
SidebarMenuItem,
2727
useSidebar,
2828
} from "@/components/ui/sidebar"
29+
import { createClient } from "@/lib/supabase/client"
30+
import { useRouter } from "next/navigation"
2931

3032
export function NavUser({
3133
user,
@@ -37,6 +39,17 @@ export function NavUser({
3739
}
3840
}) {
3941
const { isMobile } = useSidebar()
42+
const router = useRouter()
43+
44+
const handleSignOut = async () => {
45+
try {
46+
const supabase = createClient()
47+
await supabase.auth.signOut()
48+
router.push("/dashboard/login")
49+
} catch {
50+
router.push("/dashboard/login")
51+
}
52+
}
4053

4154
return (
4255
<SidebarMenu>
@@ -88,9 +101,9 @@ export function NavUser({
88101
</DropdownMenuItem>
89102
</DropdownMenuGroup>
90103
<DropdownMenuSeparator />
91-
<DropdownMenuItem>
104+
<DropdownMenuItem onClick={handleSignOut}>
92105
<LogOut />
93-
Log out
106+
Sign out
94107
</DropdownMenuItem>
95108
</DropdownMenuContent>
96109
</DropdownMenu>

lib/supabase/client.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ import { createBrowserClient } from "@supabase/ssr";
22

33
/**
44
* Creates a Supabase client for use in Client Components (browser).
5-
*
6-
* Usage:
7-
* import { createClient } from "@/lib/supabase/client";
8-
* const supabase = createClient();
5+
* Requires NEXT_PUBLIC_ prefixed env vars (exposed to browser by Next.js).
96
*/
107
export function createClient() {
118
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;

lib/supabase/server.ts

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,16 @@ import { cookies } from "next/headers";
44
/**
55
* Creates a Supabase client for use in Server Components, Route Handlers,
66
* and Server Actions.
7-
*
8-
* Usage:
9-
* import { createClient } from "@/lib/supabase/server";
10-
* const supabase = await createClient();
117
*/
128
export async function createClient() {
13-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
14-
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
9+
const supabaseUrl =
10+
process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL;
11+
const supabaseAnonKey =
12+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY;
1513

1614
if (!supabaseUrl || !supabaseAnonKey) {
1715
throw new Error(
18-
"Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables",
16+
"Missing Supabase environment variables (NEXT_PUBLIC_SUPABASE_URL or SUPABASE_URL)",
1917
);
2018
}
2119

@@ -32,9 +30,8 @@ export async function createClient() {
3230
cookieStore.set(name, value, options);
3331
}
3432
} catch {
35-
// The `setAll` method was called from a Server Component.
36-
// This can be ignored if you have middleware refreshing
37-
// user sessions.
33+
// The setAll method was called from a Server Component.
34+
// This can be ignored if you have proxy refreshing user sessions.
3835
}
3936
},
4037
},
@@ -44,22 +41,18 @@ export async function createClient() {
4441
/**
4542
* Creates a Supabase admin client using the service role key.
4643
* WARNING: This bypasses Row Level Security — use only in trusted server contexts.
47-
*
48-
* Usage:
49-
* import { createAdminClient } from "@/lib/supabase/server";
50-
* const supabase = createAdminClient();
5144
*/
5245
export function createAdminClient() {
53-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
46+
const supabaseUrl =
47+
process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL;
5448
const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
5549

5650
if (!supabaseUrl || !supabaseServiceRoleKey) {
5751
throw new Error(
58-
"Missing NEXT_PUBLIC_SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY environment variables",
52+
"Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY environment variables",
5953
);
6054
}
6155

62-
// Import directly to avoid cookie overhead for admin operations
6356
const { createClient } = require("@supabase/supabase-js");
6457
return createClient(supabaseUrl, supabaseServiceRoleKey, {
6558
auth: {

proxy.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,19 @@ import { NextResponse, type NextRequest } from "next/server";
44
export async function proxy(request: NextRequest) {
55
let supabaseResponse = NextResponse.next({ request });
66

7-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
8-
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
7+
// Support both NEXT_PUBLIC_ and non-prefixed env var names
8+
const supabaseUrl =
9+
process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL;
10+
const supabaseAnonKey =
11+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY;
912

1013
if (!supabaseUrl || !supabaseAnonKey) {
14+
// Auth not configured — block access to dashboard
15+
const url = request.nextUrl.clone();
16+
url.pathname = "/dashboard/login";
17+
if (request.nextUrl.pathname !== "/dashboard/login") {
18+
return NextResponse.redirect(url);
19+
}
1120
return supabaseResponse;
1221
}
1322

@@ -34,10 +43,12 @@ export async function proxy(request: NextRequest) {
3443

3544
const { pathname } = request.nextUrl;
3645

46+
// Allow login and auth callback pages without auth
3747
if (
3848
pathname === "/dashboard/login" ||
3949
pathname.startsWith("/dashboard/auth/")
4050
) {
51+
// If already logged in, redirect to dashboard
4152
if (user) {
4253
const url = request.nextUrl.clone();
4354
url.pathname = "/dashboard";
@@ -46,6 +57,7 @@ export async function proxy(request: NextRequest) {
4657
return supabaseResponse;
4758
}
4859

60+
// Protect all other /dashboard routes
4961
if (pathname.startsWith("/dashboard") && !user) {
5062
const url = request.nextUrl.clone();
5163
url.pathname = "/dashboard/login";
@@ -56,5 +68,6 @@ export async function proxy(request: NextRequest) {
5668
}
5769

5870
export const config = {
71+
// ONLY match dashboard routes — never touch the main site
5972
matcher: ["/dashboard/:path*"],
6073
};

0 commit comments

Comments
 (0)