Skip to content

Commit 7112c64

Browse files
committed
cleanup providers;
1 parent 56e2b2b commit 7112c64

File tree

8 files changed

+228
-158
lines changed

8 files changed

+228
-158
lines changed

apps/dashboard/app/(main)/organizations/components/organization-provider.tsx

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

3+
import type { Icon as PhosphorIcon } from "@phosphor-icons/react";
34
import {
45
BuildingsIcon,
56
EnvelopeIcon,
67
GearIcon,
78
GlobeIcon,
89
KeyIcon,
9-
PlusIcon,
10-
UserPlusIcon,
1110
UsersIcon,
1211
WarningIcon,
1312
} from "@phosphor-icons/react";
13+
import { useAtomValue } from "jotai";
1414
import { usePathname } from "next/navigation";
15-
import { useState } from "react";
15+
import { useMemo, useState } from "react";
1616

1717
import { CreateOrganizationDialog } from "@/components/organizations/create-organization-dialog";
1818
import { InviteMemberDialog } from "@/components/organizations/invite-member-dialog";
1919
import { Button } from "@/components/ui/button";
2020
import { Skeleton } from "@/components/ui/skeleton";
21-
import { useOrganizations } from "@/hooks/use-organizations";
21+
import {
22+
activeOrganizationAtom,
23+
isLoadingOrganizationsAtom,
24+
} from "@/stores/jotai/organizationsAtoms";
25+
26+
type PageInfo = {
27+
title: string;
28+
description: string;
29+
icon: PhosphorIcon;
30+
requiresOrg?: boolean;
31+
};
32+
33+
const PAGE_INFO_MAP: Record<string, PageInfo> = {
34+
"/organizations": {
35+
title: "Organizations",
36+
description: "Manage your organizations and team collaboration",
37+
icon: BuildingsIcon,
38+
},
39+
"/organizations/members": {
40+
title: "Team Members",
41+
description: "Manage team members and their roles",
42+
icon: UsersIcon,
43+
requiresOrg: true,
44+
},
45+
"/organizations/invitations": {
46+
title: "Pending Invitations",
47+
description: "View and manage pending team invitations",
48+
icon: EnvelopeIcon,
49+
requiresOrg: true,
50+
},
51+
"/organizations/settings": {
52+
title: "General Settings",
53+
description: "Manage organization name, slug, and basic settings",
54+
icon: GearIcon,
55+
requiresOrg: true,
56+
},
57+
"/organizations/settings/websites": {
58+
title: "Website Management",
59+
description: "Manage websites associated with this organization",
60+
icon: GlobeIcon,
61+
requiresOrg: true,
62+
},
63+
"/organizations/settings/api-keys": {
64+
title: "API Keys",
65+
description: "Create and manage API keys for this organization",
66+
icon: KeyIcon,
67+
requiresOrg: true,
68+
},
69+
"/organizations/settings/danger": {
70+
title: "Danger Zone",
71+
description: "Irreversible and destructive actions",
72+
icon: WarningIcon,
73+
requiresOrg: true,
74+
},
75+
};
76+
77+
const DEFAULT_PAGE_INFO: PageInfo = {
78+
title: "Organizations",
79+
description: "Manage your organizations and team collaboration",
80+
icon: BuildingsIcon,
81+
};
2282

2383
export function OrganizationProvider({
2484
children,
2585
}: {
2686
children: React.ReactNode;
2787
}) {
28-
const { activeOrganization, isLoading } = useOrganizations();
88+
// Subscribe directly to atoms - no hook overhead
89+
const activeOrganization = useAtomValue(activeOrganizationAtom);
90+
const isLoading = useAtomValue(isLoadingOrganizationsAtom);
91+
2992
const pathname = usePathname();
3093
const [showCreateDialog, setShowCreateDialog] = useState(false);
3194
const [showInviteMemberDialog, setShowInviteMemberDialog] = useState(false);
3295

33-
const getPageInfo = () => {
34-
if (pathname === "/organizations") {
35-
return {
36-
title: "Organizations",
37-
description: "Manage your organizations and team collaboration",
38-
icon: BuildingsIcon,
39-
};
40-
}
41-
if (pathname === "/organizations/members") {
42-
return {
43-
title: "Team Members",
44-
description: "Manage team members and their roles",
45-
icon: UsersIcon,
46-
requiresOrg: true,
47-
};
48-
}
49-
if (pathname === "/organizations/invitations") {
50-
return {
51-
title: "Pending Invitations",
52-
description: "View and manage pending team invitations",
53-
icon: EnvelopeIcon,
54-
requiresOrg: true,
55-
};
56-
}
57-
if (pathname === "/organizations/settings") {
58-
return {
59-
title: "General Settings",
60-
description: "Manage organization name, slug, and basic settings",
61-
icon: GearIcon,
62-
requiresOrg: true,
63-
};
64-
}
65-
if (pathname === "/organizations/settings/websites") {
66-
return {
67-
title: "Website Management",
68-
description: "Manage websites associated with this organization",
69-
icon: GlobeIcon,
70-
requiresOrg: true,
71-
};
72-
}
73-
if (pathname === "/organizations/settings/api-keys") {
74-
return {
75-
title: "API Keys",
76-
description: "Create and manage API keys for this organization",
77-
icon: KeyIcon,
78-
requiresOrg: true,
79-
};
80-
}
81-
if (pathname === "/organizations/settings/danger") {
82-
return {
83-
title: "Danger Zone",
84-
description: "Irreversible and destructive actions",
85-
icon: WarningIcon,
86-
requiresOrg: true,
87-
};
88-
}
89-
return {
90-
title: "Organizations",
91-
description: "Manage your organizations and team collaboration",
92-
icon: BuildingsIcon,
93-
};
94-
};
95-
9696
const {
9797
title,
9898
description,
9999
icon: Icon,
100100
requiresOrg,
101-
} = getPageInfo();
101+
} = useMemo(() => PAGE_INFO_MAP[pathname] ?? DEFAULT_PAGE_INFO, [pathname]);
102102

103103
if (isLoading) {
104104
return (

apps/dashboard/app/layout.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ export default function RootLayout({
133133
trackAttributes={true}
134134
trackErrors={true}
135135
trackPerformance={true}
136-
trackScreenViews={true}
137136
trackWebVitals={true}
138137
/>
139138
<body className="flex h-full min-h-screen flex-col bg-background text-foreground antialiased">

apps/dashboard/app/providers.tsx

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

33
import { authClient } from "@databuddy/auth/client";
44
import { FlagsProvider } from "@databuddy/sdk/react";
5-
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5+
import {
6+
QueryClient,
7+
QueryClientProvider,
8+
useQuery,
9+
} from "@tanstack/react-query";
610
import { AutumnProvider } from "autumn-js/react";
711
import { ThemeProvider } from "next-themes";
812
import { NuqsAdapter } from "nuqs/adapters/next/app";
@@ -62,8 +66,20 @@ export default function Providers({ children }: { children: React.ReactNode }) {
6266
);
6367
}
6468

69+
// Query key for session - shared with other components for deduplication
70+
export const SESSION_QUERY_KEY = ["auth", "session"] as const;
71+
6572
function FlagsProviderWrapper({ children }: { children: React.ReactNode }) {
66-
const { data: session, isPending } = authClient.useSession();
73+
const { data: session, isPending } = useQuery({
74+
queryKey: SESSION_QUERY_KEY,
75+
queryFn: async () => {
76+
const result = await authClient.getSession();
77+
return result.data;
78+
},
79+
staleTime: 2 * 60 * 1000, // 2 minutes
80+
gcTime: 5 * 60 * 1000, // 5 minutes
81+
});
82+
6783
return (
6884
<FlagsProvider
6985
clientId="3ed1fce1-5a56-4cb6-a977-66864f6d18e3"

apps/dashboard/components/providers/organizations-provider.tsx

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"use client";
22

33
import { authClient } from "@databuddy/auth/client";
4-
import { useAtom } from "jotai";
4+
import { useQuery } from "@tanstack/react-query";
5+
import { useAtom, useAtomValue, useSetAtom } from "jotai";
56
import { type ReactNode, useEffect } from "react";
67
import {
78
activeOrganizationAtom,
@@ -14,38 +15,62 @@ export type Organization = NonNullable<
1415
ReturnType<typeof authClient.useListOrganizations>["data"]
1516
>[number];
1617

18+
export const AUTH_QUERY_KEYS = {
19+
session: ["auth", "session"] as const,
20+
organizations: ["auth", "organizations"] as const,
21+
activeOrganization: ["auth", "activeOrganization"] as const,
22+
} as const;
23+
1724
export function OrganizationsProvider({ children }: { children: ReactNode }) {
18-
const { data: organizationsData, isPending: isLoadingOrgs } =
19-
authClient.useListOrganizations();
20-
const { data: activeOrganization, isPending: isLoadingActive } =
21-
authClient.useActiveOrganization();
25+
const setOrganizations = useSetAtom(organizationsAtom);
26+
const setActiveOrganization = useSetAtom(activeOrganizationAtom);
27+
const setIsLoading = useSetAtom(isLoadingOrganizationsAtom);
28+
29+
// Fetch organizations with TanStack Query (cached & deduplicated)
30+
const { data: organizationsData, isPending: isLoadingOrgs } = useQuery({
31+
queryKey: AUTH_QUERY_KEYS.organizations,
32+
queryFn: async () => {
33+
const result = await authClient.organization.list();
34+
return result.data ?? [];
35+
},
36+
staleTime: 2 * 60 * 1000, // 2 minutes
37+
gcTime: 5 * 60 * 1000, // 5 minutes
38+
});
2239

40+
// Fetch active organization with TanStack Query (cached & deduplicated)
41+
const { data: activeOrganization, isPending: isLoadingActive } = useQuery({
42+
queryKey: AUTH_QUERY_KEYS.activeOrganization,
43+
queryFn: async () => {
44+
// getFullOrganization returns the currently active org when no ID specified
45+
const result = await authClient.organization.getFullOrganization();
46+
return result.data ?? null;
47+
},
48+
staleTime: 2 * 60 * 1000,
49+
gcTime: 5 * 60 * 1000,
50+
});
2351

24-
const [, setOrganizations] = useAtom(organizationsAtom);
25-
const [, setActiveOrganization] = useAtom(activeOrganizationAtom);
26-
const [, setIsLoading] = useAtom(isLoadingOrganizationsAtom);
52+
// Sync to atoms in useEffect to avoid setState during render
53+
useEffect(() => {
54+
if (organizationsData) {
55+
setOrganizations(organizationsData);
56+
}
57+
}, [organizationsData, setOrganizations]);
2758

2859
useEffect(() => {
29-
setOrganizations(organizationsData ?? []);
3060
setActiveOrganization(activeOrganization ?? null);
61+
}, [activeOrganization, setActiveOrganization]);
62+
63+
useEffect(() => {
3164
setIsLoading(isLoadingOrgs || isLoadingActive);
32-
}, [
33-
organizationsData,
34-
activeOrganization,
35-
isLoadingOrgs,
36-
isLoadingActive,
37-
setOrganizations,
38-
setActiveOrganization,
39-
setIsLoading,
40-
]);
65+
}, [isLoadingOrgs, isLoadingActive, setIsLoading]);
4166

4267
return <>{children}</>;
4368
}
4469

4570
export function useOrganizationsContext() {
46-
const [organizations] = useAtom(organizationsAtom);
47-
const [activeOrganization] = useAtom(activeOrganizationAtom);
48-
const [isLoading] = useAtom(isLoadingOrganizationsAtom);
71+
const organizations = useAtomValue(organizationsAtom);
72+
const activeOrganization = useAtomValue(activeOrganizationAtom);
73+
const isLoading = useAtomValue(isLoadingOrganizationsAtom);
4974
const [getOrganizationBySlug] = useAtom(getOrganizationBySlugAtom);
5075

5176
return {

0 commit comments

Comments
 (0)