"use client"; import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react"; import { getMe, getTenant, refreshAuth } from "@/lib/meshkee"; import { BUSINESS_URL, CUSTOMER_URL, TOKEN_KEYS } from "@/lib/config"; import { displayName, isWebsiteAdmin } from "@/lib/format"; import type { MeshkeeUser } from "@/lib/types"; type AuthContextValue = { user: MeshkeeUser | null; loading: boolean; isAdmin: boolean; label: string; customerUrl: string; businessUrl: string; profileUrl: string; loginUrl: string; logout: () => void; refresh: () => Promise; }; const AuthContext = createContext(null); function readToken(key: string) { try { return localStorage.getItem(key); } catch { return null; } } function writeTokens(access?: string | null, refresh?: string | null) { try { if (access) localStorage.setItem(TOKEN_KEYS.access, access); else localStorage.removeItem(TOKEN_KEYS.access); if (refresh) localStorage.setItem(TOKEN_KEYS.refresh, refresh); else localStorage.removeItem(TOKEN_KEYS.refresh); } catch { /* ignore */ } } function captureTokensFromUrl() { if (typeof window === "undefined") return; const url = new URL(window.location.href); const hash = new URLSearchParams(url.hash.replace(/^#/, "")); const access = url.searchParams.get("accessToken") || url.searchParams.get("token") || hash.get("accessToken") || hash.get("token"); const refresh = url.searchParams.get("refreshToken") || hash.get("refreshToken"); if (!access && !refresh) return; writeTokens(access, refresh); url.searchParams.delete("accessToken"); url.searchParams.delete("refreshToken"); url.searchParams.delete("token"); url.hash = ""; window.history.replaceState({}, "", `${url.pathname}${url.search}`); } export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [businessId, setBusinessId] = useState(null); const loginUrl = useMemo(() => { const returnTo = typeof window !== "undefined" ? window.location.origin : "https://www.plift.ir"; return `${CUSTOMER_URL}?redirect=${encodeURIComponent(returnTo)}`; }, []); const refresh = useCallback(async () => { captureTokensFromUrl(); const access = readToken(TOKEN_KEYS.access); const refreshToken = readToken(TOKEN_KEYS.refresh); if (!access && !refreshToken) { setUser(null); setLoading(false); return; } try { let token = access; if (!token && refreshToken) { const refreshed = await refreshAuth(refreshToken); writeTokens(refreshed.accessToken, refreshed.refreshToken); token = refreshed.accessToken; if (refreshed.user) { setUser(refreshed.user); setLoading(false); return; } } if (!token) { setUser(null); return; } const me = await getMe(token); setUser(me); } catch { if (refreshToken) { try { const refreshed = await refreshAuth(refreshToken); writeTokens(refreshed.accessToken, refreshed.refreshToken); const me = refreshed.user || (await getMe(refreshed.accessToken)); setUser(me); return; } catch { writeTokens(null, null); setUser(null); } } else { writeTokens(null, null); setUser(null); } } finally { setLoading(false); } }, []); useEffect(() => { let cancelled = false; (async () => { try { const tenant = await getTenant(); if (!cancelled) setBusinessId(tenant.id); } catch { /* ignore */ } if (!cancelled) await refresh(); })(); return () => { cancelled = true; }; }, [refresh]); const logout = useCallback(() => { writeTokens(null, null); setUser(null); }, []); const value: AuthContextValue = { user, loading, isAdmin: user ? isWebsiteAdmin(user, businessId) : false, label: user ? `${displayName(user)} عزیز` : "", customerUrl: CUSTOMER_URL, businessUrl: BUSINESS_URL, profileUrl: `${CUSTOMER_URL}/profile`, loginUrl, logout, refresh, }; return {children}; } export function useAuth() { const ctx = useContext(AuthContext); if (!ctx) throw new Error("useAuth must be used within AuthProvider"); return ctx; }