aboutsummaryrefslogtreecommitdiff
path: root/packages/lib/auth-context.tsx
blob: 4bfdc2d75e8addbdb70be10774eec94a38c2e1b2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"use client"

import {
	createContext,
	type ReactNode,
	useContext,
	useEffect,
	useState,
} from "react"
import { authClient, useSession } from "./auth"

type Organization = typeof authClient.$Infer.ActiveOrganization
type SessionData = NonNullable<ReturnType<typeof useSession>["data"]>

interface AuthContextType {
	session: SessionData["session"] | null
	user: SessionData["user"] | null
	org: Organization | null
	setActiveOrg: (orgSlug: string) => Promise<void>
}

const AuthContext = createContext<AuthContextType | undefined>(undefined)

export function AuthProvider({ children }: { children: ReactNode }) {
	const { data: session } = useSession()
	const [org, setOrg] = useState<Organization | null>(null)
	const { data: orgs } = authClient.useListOrganizations()

	const setActiveOrg = async (slug: string) => {
		if (!slug) return

		const activeOrg = await authClient.organization.setActive({
			organizationSlug: slug,
		})
		setOrg(activeOrg)
	}

	// biome-ignore lint/correctness/useExhaustiveDependencies: ignoring the setActiveOrg dependency
	useEffect(() => {
		if (session?.session.activeOrganizationId) {
			authClient.organization
				.getFullOrganization()
				.then((org) => {
					if (org.metadata?.isConsumer === true) {
						console.log("Consumer organization:", org)
						setOrg(org)
					} else {
						console.log("ALl orgs:", orgs)
						const consumerOrg = orgs?.find(
							(o) => o.metadata?.isConsumer === true,
						)
						if (consumerOrg) {
							setActiveOrg(consumerOrg.slug)
						}
					}
				})
				.catch((error) => {
					// Silently handle organization fetch failures to prevent unhandled rejections
					console.error("Failed to fetch organization:", error)
				})
		}
	}, [session?.session.activeOrganizationId, orgs])

	// When a session exists and there is a pending login method recorded,
	// promote it to the last-used method (successful login) and clear pending.
	useEffect(() => {
		if (typeof window === "undefined") return
		if (!session?.session) return

		try {
			const pendingMethod = localStorage.getItem(
				"supermemory-pending-login-method",
			)
			const pendingTsRaw = localStorage.getItem(
				"supermemory-pending-login-timestamp",
			)

			if (pendingMethod) {
				const now = Date.now()
				const ts = pendingTsRaw ? Number.parseInt(pendingTsRaw, 10) : Number.NaN
				const isFresh = Number.isFinite(ts) && now - ts < 10 * 60 * 1000 // 10 minutes TTL

				if (isFresh) {
					localStorage.setItem("supermemory-last-login-method", pendingMethod)
				}
			}
		} catch {}
		// Always clear pending markers once a session is present
		try {
			localStorage.removeItem("supermemory-pending-login-method")
			localStorage.removeItem("supermemory-pending-login-timestamp")
		} catch {}
	}, [session?.session])

	return (
		<AuthContext.Provider
			value={{
				org,
				session: session?.session ?? null,
				user: session?.user ?? null,
				setActiveOrg,
			}}
		>
			{children}
		</AuthContext.Provider>
	)
}

export function useAuth() {
	const context = useContext(AuthContext)
	if (context === undefined) {
		throw new Error("useAuth must be used within an AuthProvider")
	}
	return context
}