Skip to content

Commit 5102bf4

Browse files
committed
server-side auth
1 parent 19cda7f commit 5102bf4

File tree

7 files changed

+278
-136
lines changed

7 files changed

+278
-136
lines changed

apps/dashboard/app/(main)/layout.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
1+
import { auth } from "@databuddy/auth";
12
import { AutumnProvider } from "autumn-js/react";
3+
import { headers } from "next/headers";
24
import { DevToolsDrawer } from "@/components/dev-tools/dev-tools-drawer";
35
import { Sidebar } from "@/components/layout/sidebar";
46
import { BillingProvider } from "@/components/providers/billing-provider";
57
import { CommandSearch } from "@/components/ui/command-search";
68

7-
export default function MainLayout({
9+
export default async function MainLayout({
810
children,
911
}: {
1012
children: React.ReactNode;
1113
}) {
14+
const headersList = await headers();
15+
const session = await auth.api.getSession({
16+
headers: headersList,
17+
});
18+
19+
const user = session?.user || {
20+
name: null,
21+
email: null,
22+
image: null,
23+
};
24+
1225
return (
1326
<AutumnProvider
1427
backendUrl={process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001"}
1528
>
1629
<BillingProvider>
1730
<div className="h-screen overflow-hidden text-foreground">
18-
<Sidebar />
31+
<Sidebar user={user} />
1932
<CommandSearch />
2033
<DevToolsDrawer />
2134
<div className="relative h-screen pl-0 md:pl-76 lg:pl-84">

apps/dashboard/app/layout.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import "./globals.css";
33
import { Databuddy } from "@databuddy/sdk/react";
44
import type { Metadata, Viewport } from "next";
55
import { Geist, Geist_Mono } from "next/font/google";
6+
import Script from "next/script";
67
import { Toaster } from "@/components/ui/sonner";
78
import Providers from "./providers";
89

@@ -114,6 +115,10 @@ export default function RootLayout({
114115
lang="en"
115116
suppressHydrationWarning
116117
>
118+
<Script
119+
crossOrigin="anonymous"
120+
src="//unpkg.com/react-scan/dist/auto.global.js"
121+
/>
117122
<Databuddy
118123
apiUrl={
119124
isLocalhost ? "http://localhost:4000" : "https://basket.databuddy.cc"

apps/dashboard/components/layout/category-sidebar.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
getContextConfig,
2424
getDefaultCategory,
2525
} from "./navigation/navigation-config";
26-
import { ProfileButton } from "./profile-button";
26+
import { ProfileButtonClient } from "./profile-button-client";
2727
import { ThemeToggle } from "./theme-toggle";
2828

2929
const HelpDialog = dynamic(
@@ -34,14 +34,22 @@ const HelpDialog = dynamic(
3434
}
3535
);
3636

37+
type User = {
38+
name?: string | null;
39+
email?: string | null;
40+
image?: string | null;
41+
};
42+
3743
type CategorySidebarProps = {
3844
onCategoryChangeAction?: (categoryId: string) => void;
3945
selectedCategory?: string;
46+
user: User;
4047
};
4148

4249
export function CategorySidebar({
4350
onCategoryChangeAction,
4451
selectedCategory,
52+
user,
4553
}: CategorySidebarProps) {
4654
const pathname = usePathname();
4755
const { websites, isLoading: isLoadingWebsites } = useWebsites();
@@ -103,8 +111,8 @@ export function CategorySidebar({
103111
const Icon = category.icon;
104112
const isActive = activeCategory === category.id;
105113
const isLast = idx === categories.length - 1;
106-
const shouldShowBorder = isActive && !isLast;
107-
const borderClass = shouldShowBorder ? "border-accent" : "";
114+
// biome-ignore lint/nursery/noLeakedRender: FUCK ULTRACITE BRO THIS MAKES NO SENSE
115+
const borderClass = isActive && !isLast ? "border-accent" : "";
108116
const hoverClass = isActive ? "" : "hover:bg-sidebar-accent-brighter";
109117
const boxClass = isLast
110118
? "box-content border-border border-b"
@@ -171,7 +179,7 @@ export function CategorySidebar({
171179
</div>
172180

173181
<div className="flex justify-center">
174-
<ProfileButton />
182+
<ProfileButtonClient user={user} />
175183
</div>
176184
</div>
177185

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"use client";
2+
3+
import { authClient } from "@databuddy/auth/client";
4+
import { GearIcon, SignOutIcon } from "@phosphor-icons/react";
5+
import { useRouter } from "next/navigation";
6+
import { useState } from "react";
7+
import { toast } from "sonner";
8+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
9+
import {
10+
DropdownMenu,
11+
DropdownMenuContent,
12+
DropdownMenuItem,
13+
DropdownMenuLabel,
14+
DropdownMenuSeparator,
15+
DropdownMenuTrigger,
16+
} from "@/components/ui/dropdown-menu";
17+
import {
18+
Tooltip,
19+
TooltipContent,
20+
TooltipTrigger,
21+
} from "@/components/ui/tooltip";
22+
23+
type User = {
24+
name?: string | null;
25+
email?: string | null;
26+
image?: string | null;
27+
};
28+
29+
type ProfileButtonClientProps = {
30+
user: User;
31+
};
32+
33+
export function ProfileButtonClient({ user }: ProfileButtonClientProps) {
34+
const [isLoggingOut, setIsLoggingOut] = useState(false);
35+
const [isOpen, setIsOpen] = useState(false);
36+
const router = useRouter();
37+
38+
const handleLogout = async () => {
39+
setIsLoggingOut(true);
40+
setIsOpen(false);
41+
await authClient.signOut({
42+
fetchOptions: {
43+
onSuccess: () => {
44+
toast.success("Logged out successfully");
45+
router.push("/login");
46+
},
47+
onError: (error) => {
48+
router.push("/login");
49+
toast.error(error.error.message || "Failed to log out");
50+
},
51+
},
52+
});
53+
setIsLoggingOut(false);
54+
};
55+
56+
const handleSettings = () => {
57+
setIsOpen(false);
58+
router.push("/settings/account");
59+
};
60+
61+
const userInitials = user?.name
62+
? user.name
63+
.split(" ")
64+
.map((n) => n[0])
65+
.join("")
66+
.toUpperCase()
67+
.slice(0, 2)
68+
: user?.email?.[0]?.toUpperCase() || "U";
69+
70+
return (
71+
<DropdownMenu onOpenChange={setIsOpen} open={isOpen}>
72+
<Tooltip>
73+
<TooltipTrigger asChild>
74+
<DropdownMenuTrigger
75+
aria-label="Profile menu"
76+
className="flex size-8 items-center justify-center rounded-full outline-hidden transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
77+
disabled={isLoggingOut}
78+
>
79+
<Avatar className="size-8">
80+
<AvatarImage
81+
alt={user?.name || "User"}
82+
src={user?.image || undefined}
83+
/>
84+
<AvatarFallback className="bg-primary text-primary-foreground text-xs">
85+
{userInitials}
86+
</AvatarFallback>
87+
</Avatar>
88+
</DropdownMenuTrigger>
89+
</TooltipTrigger>
90+
<TooltipContent side="right">
91+
<p>Profile menu</p>
92+
</TooltipContent>
93+
</Tooltip>
94+
95+
<DropdownMenuContent align="start" className="w-56" side="right">
96+
<DropdownMenuLabel>
97+
<div className="flex flex-col space-y-1">
98+
<p className="font-medium text-sm leading-none">{user?.name}</p>
99+
<p className="text-muted-foreground text-xs leading-none">
100+
{user?.email}
101+
</p>
102+
</div>
103+
</DropdownMenuLabel>
104+
<DropdownMenuSeparator />
105+
<DropdownMenuItem onClick={handleSettings}>
106+
<GearIcon weight="duotone" />
107+
Settings
108+
</DropdownMenuItem>
109+
<DropdownMenuSeparator />
110+
<DropdownMenuItem
111+
disabled={isLoggingOut}
112+
onClick={handleLogout}
113+
variant="destructive"
114+
>
115+
<SignOutIcon weight="duotone" />
116+
{isLoggingOut ? "Signing out..." : "Sign out"}
117+
</DropdownMenuItem>
118+
</DropdownMenuContent>
119+
</DropdownMenu>
120+
);
121+
}
Lines changed: 13 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,18 @@
1-
"use client";
1+
import { auth } from "@databuddy/auth";
2+
import { headers } from "next/headers";
3+
import { ProfileButtonClient } from "./profile-button-client";
24

3-
import { authClient } from "@databuddy/auth/client";
4-
import { GearIcon, SignOutIcon } from "@phosphor-icons/react";
5-
import { useRouter } from "next/navigation";
6-
import { useState } from "react";
7-
import { toast } from "sonner";
8-
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
9-
import {
10-
DropdownMenu,
11-
DropdownMenuContent,
12-
DropdownMenuItem,
13-
DropdownMenuLabel,
14-
DropdownMenuSeparator,
15-
DropdownMenuTrigger,
16-
} from "@/components/ui/dropdown-menu";
17-
import {
18-
Tooltip,
19-
TooltipContent,
20-
TooltipTrigger,
21-
} from "@/components/ui/tooltip";
5+
export async function ProfileButton() {
6+
const headersList = await headers();
7+
const session = await auth.api.getSession({
8+
headers: headersList,
9+
});
2210

23-
export function ProfileButton() {
24-
const [isLoggingOut, setIsLoggingOut] = useState(false);
25-
const [isOpen, setIsOpen] = useState(false);
26-
const router = useRouter();
27-
const { data: session, isPending: isSessionLoading } =
28-
authClient.useSession();
29-
30-
const handleLogout = async () => {
31-
setIsLoggingOut(true);
32-
setIsOpen(false);
33-
await authClient.signOut({
34-
fetchOptions: {
35-
onSuccess: () => {
36-
toast.success("Logged out successfully");
37-
router.push("/login");
38-
},
39-
onError: (error) => {
40-
router.push("/login");
41-
toast.error(error.error.message || "Failed to log out");
42-
},
43-
},
44-
});
45-
setIsLoggingOut(false);
46-
};
47-
48-
const handleSettings = () => {
49-
setIsOpen(false);
50-
router.push("/settings/account");
11+
const user = session?.user || {
12+
name: null,
13+
email: null,
14+
image: null,
5115
};
5216

53-
const user = session?.user;
54-
const userInitials = user?.name
55-
? user.name
56-
.split(" ")
57-
.map((n) => n[0])
58-
.join("")
59-
.toUpperCase()
60-
.slice(0, 2)
61-
: user?.email?.[0]?.toUpperCase() || "U";
62-
63-
return (
64-
<DropdownMenu onOpenChange={setIsOpen} open={isOpen}>
65-
<Tooltip>
66-
<TooltipTrigger asChild>
67-
<DropdownMenuTrigger
68-
aria-label="Profile menu"
69-
className="flex size-8 items-center justify-center rounded-full outline-hidden transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
70-
disabled={isLoggingOut || isSessionLoading}
71-
>
72-
<Avatar className="size-8">
73-
<AvatarImage
74-
alt={user?.name || "User"}
75-
src={user?.image || undefined}
76-
/>
77-
<AvatarFallback className="bg-primary text-primary-foreground text-xs">
78-
{userInitials}
79-
</AvatarFallback>
80-
</Avatar>
81-
</DropdownMenuTrigger>
82-
</TooltipTrigger>
83-
<TooltipContent side="right">
84-
<p>Profile menu</p>
85-
</TooltipContent>
86-
</Tooltip>
87-
88-
<DropdownMenuContent align="start" className="w-56" side="right">
89-
<DropdownMenuLabel>
90-
<div className="flex flex-col space-y-1">
91-
<p className="font-medium text-sm leading-none">{user?.name}</p>
92-
<p className="text-muted-foreground text-xs leading-none">
93-
{user?.email}
94-
</p>
95-
</div>
96-
</DropdownMenuLabel>
97-
<DropdownMenuSeparator />
98-
<DropdownMenuItem onClick={handleSettings}>
99-
<GearIcon weight="duotone" />
100-
Settings
101-
</DropdownMenuItem>
102-
<DropdownMenuSeparator />
103-
<DropdownMenuItem
104-
disabled={isLoggingOut}
105-
onClick={handleLogout}
106-
variant="destructive"
107-
>
108-
<SignOutIcon weight="duotone" />
109-
{isLoggingOut ? "Signing out..." : "Sign out"}
110-
</DropdownMenuItem>
111-
</DropdownMenuContent>
112-
</DropdownMenu>
113-
);
17+
return <ProfileButtonClient user={user} />;
11418
}

apps/dashboard/components/layout/sidebar.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,17 @@ type NavigationConfig = {
3131
currentWebsiteId?: string | null;
3232
};
3333

34-
export function Sidebar() {
34+
type User = {
35+
name?: string | null;
36+
email?: string | null;
37+
image?: string | null;
38+
};
39+
40+
type SidebarProps = {
41+
user: User;
42+
};
43+
44+
export function Sidebar({ user }: SidebarProps) {
3545
const pathname = usePathname();
3646
const [isMobileOpen, setIsMobileOpen] = useState(false);
3747
const [selectedCategory, setSelectedCategory] = useState<string | undefined>(
@@ -210,6 +220,7 @@ export function Sidebar() {
210220
<CategorySidebar
211221
onCategoryChangeAction={setSelectedCategory}
212222
selectedCategory={selectedCategory}
223+
user={user}
213224
/>
214225
</div>
215226

0 commit comments

Comments
 (0)