UNPKG

@daveyplate/better-auth-ui

Version:

Plug & play shadcn/ui components for better-auth

826 lines (777 loc) 26.1 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } var _class;"use client" var _chunk2IFYYEVLcjs = require('./chunk-2IFYYEVL.cjs'); // src/hooks/use-auth-data.ts var _react = require('react'); // src/lib/auth-data-cache.ts var AuthDataCache = (_class = class {constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this); } __init() {this.cache = /* @__PURE__ */ new Map()} __init2() {this.listeners = /* @__PURE__ */ new Map()} __init3() {this.inFlightRequests = /* @__PURE__ */ new Map()} get(key) { return this.cache.get(key); } set(key, data) { const entry = { data, timestamp: Date.now(), isRefetching: false }; this.cache.set(key, entry); this.notify(key); } setRefetching(key, isRefetching) { const entry = this.cache.get(key); if (entry) { entry.isRefetching = isRefetching; this.notify(key); } } clear(key) { if (key) { this.cache.delete(key); this.inFlightRequests.delete(key); this.notify(key); } else { this.cache.clear(); this.inFlightRequests.clear(); const keys = Array.from(this.listeners.keys()); for (const key2 of keys) { this.notify(key2); } } } getInFlightRequest(key) { return this.inFlightRequests.get(key); } setInFlightRequest(key, promise) { this.inFlightRequests.set(key, promise); } removeInFlightRequest(key) { this.inFlightRequests.delete(key); } subscribe(key, callback) { if (!this.listeners.has(key)) { this.listeners.set(key, /* @__PURE__ */ new Set()); } this.listeners.get(key).add(callback); return () => { const callbacks = this.listeners.get(key); if (callbacks) { callbacks.delete(callback); if (callbacks.size === 0) { this.listeners.delete(key); } } }; } notify(key) { const callbacks = this.listeners.get(key); if (callbacks) { const callbackArray = Array.from(callbacks); for (const callback of callbackArray) { callback(); } } } }, _class); var authDataCache = new AuthDataCache(); // src/lib/auth-ui-provider.tsx var _sonner = require('sonner'); // src/components/captcha/recaptcha-v3.tsx var _reactrecaptchav3 = require('@wojtekmaj/react-recaptcha-v3'); // src/hooks/use-hydrated.ts function subscribe() { return () => { }; } function useIsHydrated() { return _react.useSyncExternalStore.call(void 0, subscribe, () => true, () => false ); } // src/hooks/use-lang.ts function useLang() { const [lang, setLang] = _react.useState.call(void 0, ); _react.useEffect.call(void 0, () => { const checkLang = () => { const currentLang = document.documentElement.getAttribute("lang"); setLang(_nullishCoalesce(currentLang, () => ( void 0))); }; checkLang(); const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.attributeName === "lang") { checkLang(); } } }); observer.observe(document.documentElement, { attributes: true }); return () => { observer.disconnect(); }; }, []); return { lang }; } // src/hooks/use-theme.ts function useTheme() { const [theme, setTheme] = _react.useState.call(void 0, "light"); _react.useEffect.call(void 0, () => { const checkTheme = () => { var _a; const isDark = document.documentElement.classList.contains("dark") || ((_a = document.documentElement.getAttribute("style")) == null ? void 0 : _a.includes("color-scheme: dark")); setTheme(isDark ? "dark" : "light"); }; checkTheme(); const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.attributeName === "style" || mutation.attributeName === "class") { checkTheme(); } } }); observer.observe(document.documentElement, { attributes: true }); return () => { observer.disconnect(); }; }, []); return { theme }; } // src/components/captcha/recaptcha-v3.tsx var _jsxruntime = require('react/jsx-runtime'); function RecaptchaV3({ children }) { const isHydrated = useIsHydrated(); const { captcha } = _react.useContext.call(void 0, AuthUIContext); if ((captcha == null ? void 0 : captcha.provider) !== "google-recaptcha-v3") return children; return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _reactrecaptchav3.GoogleReCaptchaProvider, { reCaptchaKey: captcha.siteKey, useEnterprise: captcha.enterprise, useRecaptchaNet: captcha.recaptchaNet, children: [ isHydrated && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "style", { children: ` .grecaptcha-badge { visibility: hidden; border-radius: var(--radius) !important; --tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, #0000000d); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) !important; border-style: var(--tw-border-style) !important; border-width: 1px; } .dark .grecaptcha-badge { border-color: var(--input) !important; } ` }), /* @__PURE__ */ _jsxruntime.jsx.call(void 0, RecaptchaV3Style, {}), children ] } ); } function RecaptchaV3Style() { const { executeRecaptcha } = _reactrecaptchav3.useGoogleReCaptcha.call(void 0, ); const { theme } = useTheme(); const { lang } = useLang(); _react.useEffect.call(void 0, () => { if (!executeRecaptcha) return; const updateRecaptcha = async () => { const iframe = document.querySelector( "iframe[title='reCAPTCHA']" ); if (iframe) { const iframeSrcUrl = new URL(iframe.src); iframeSrcUrl.searchParams.set("theme", theme); if (lang) iframeSrcUrl.searchParams.set("hl", lang); iframe.src = iframeSrcUrl.toString(); } }; updateRecaptcha(); }, [executeRecaptcha, theme, lang]); return null; } // src/lib/organization-refetcher.tsx // src/hooks/use-current-organization.ts function useCurrentOrganization({ slug: slugProp } = {}) { const { organization: organizationOptions, hooks: { useActiveOrganization, useListOrganizations } } = _react.useContext.call(void 0, AuthUIContext); const { pathMode, slug: contextSlug } = organizationOptions || {}; let data; let isPending; let isRefetching; let refetch; const { data: organizations, isPending: organizationsPending, isRefetching: organizationsRefetching } = useListOrganizations(); if (pathMode === "slug") { const slug = slugProp || contextSlug; data = organizations == null ? void 0 : organizations.find((organization) => organization.slug === slug); isPending = organizationsPending; isRefetching = organizationsRefetching; } else { const { data: activeOrganization, isPending: organizationPending, isRefetching: organizationRefetching, refetch: refetchOrganization } = useActiveOrganization(); refetch = refetchOrganization; data = activeOrganization; isPending = organizationPending; isRefetching = organizationRefetching; } return _react.useMemo.call(void 0, () => ({ data, isPending, isRefetching, refetch }), [data, isPending, isRefetching, refetch] ); } // src/lib/organization-refetcher.tsx var OrganizationRefetcher = () => { const { hooks: { useListOrganizations, useSession }, organization: organizationOptions, navigate, redirectTo } = _react.useContext.call(void 0, AuthUIContext); const { slug, pathMode, personalPath } = organizationOptions || {}; const { data: sessionData } = useSession(); const { data: organization, isPending: organizationPending, isRefetching: organizationRefetching, refetch: refetchOrganization } = useCurrentOrganization(); const { refetch: refetchListOrganizations } = useListOrganizations(); const { data: organizations } = useListOrganizations(); _react.useEffect.call(void 0, () => { if (!(sessionData == null ? void 0 : sessionData.user.id)) return; if (organization || organizations) { refetchOrganization == null ? void 0 : refetchOrganization(); refetchListOrganizations == null ? void 0 : refetchListOrganizations(); } }, [sessionData == null ? void 0 : sessionData.user.id]); _react.useEffect.call(void 0, () => { if (organizationRefetching || organizationPending) return; if (slug && pathMode === "slug" && !organization) { navigate(personalPath || redirectTo); } }, [ organization, organizationRefetching, organizationPending, slug, pathMode, personalPath, navigate, redirectTo ]); return null; }; // src/lib/auth-ui-provider.tsx var DefaultLink = ({ href, className, children }) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "a", { className, href, children }); var defaultNavigate = (href) => { window.location.href = href; }; var defaultReplace = (href) => { window.location.replace(href); }; var defaultToast = ({ variant = "default", message }) => { if (variant === "default") { _sonner.toast.call(void 0, message); } else { _sonner.toast[variant](message); } }; var AuthUIContext = _react.createContext.call(void 0, {} ); var AuthUIProvider = ({ children, authClient: authClientProp, account: accountProp, avatar: avatarProp, deleteUser: deleteUserProp, social: socialProp, genericOAuth: genericOAuthProp, basePath = "/auth", baseURL = "", captcha, redirectTo = "/", credentials: credentialsProp, changeEmail = true, freshAge = 60 * 60 * 24, hooks: hooksProp, mutators: mutatorsProp, localization: localizationProp, localizeErrors = true, nameRequired = true, organization: organizationProp, teams: teamsProp, signUp: signUpProp = true, toast: toast2 = defaultToast, viewPaths: viewPathsProp, navigate, replace, Link = DefaultLink, emailVerification: emailVerificationProp, ...props }) => { const authClient = authClientProp; const avatar = _react.useMemo.call(void 0, () => { if (!avatarProp) return; if (avatarProp === true) { return { extension: "png", size: 128 }; } return { upload: avatarProp.upload, delete: avatarProp.delete, extension: avatarProp.extension || "png", size: avatarProp.size || (avatarProp.upload ? 256 : 128), Image: avatarProp.Image }; }, [avatarProp]); const emailVerification = _react.useMemo.call(void 0, () => { if (!emailVerificationProp) return; if (emailVerificationProp === true) { return { otp: false }; } return { otp: _nullishCoalesce(emailVerificationProp.otp, () => ( false)) }; }, [emailVerificationProp]); const account = _react.useMemo.call(void 0, () => { var _a; if (accountProp === false) return; if (accountProp === true || accountProp === void 0) { return { basePath: "/account", fields: ["image", "name"], viewPaths: _chunk2IFYYEVLcjs.accountViewPaths }; } const basePath2 = ((_a = accountProp.basePath) == null ? void 0 : _a.endsWith("/")) ? accountProp.basePath.slice(0, -1) : accountProp.basePath; return { basePath: _nullishCoalesce(basePath2, () => ( "/account")), fields: accountProp.fields || ["image", "name"], viewPaths: { ..._chunk2IFYYEVLcjs.accountViewPaths, ...accountProp.viewPaths } }; }, [accountProp]); const deleteUser = _react.useMemo.call(void 0, () => { if (!deleteUserProp) return; if (deleteUserProp === true) { return {}; } return deleteUserProp; }, [deleteUserProp]); const social = _react.useMemo.call(void 0, () => { if (!socialProp) return; return socialProp; }, [socialProp]); const genericOAuth = _react.useMemo.call(void 0, () => { if (!genericOAuthProp) return; return genericOAuthProp; }, [genericOAuthProp]); const credentials = _react.useMemo.call(void 0, () => { if (credentialsProp === false) return; if (credentialsProp === true) { return { forgotPassword: true, usernameRequired: true }; } return { ...credentialsProp, forgotPassword: _nullishCoalesce((credentialsProp == null ? void 0 : credentialsProp.forgotPassword), () => ( true)), usernameRequired: _nullishCoalesce((credentialsProp == null ? void 0 : credentialsProp.usernameRequired), () => ( true)) }; }, [credentialsProp]); const signUp = _react.useMemo.call(void 0, () => { if (signUpProp === false) return; if (signUpProp === true || signUpProp === void 0) { return { fields: ["name"] }; } return { fields: signUpProp.fields || ["name"] }; }, [signUpProp]); const organization = _react.useMemo.call(void 0, () => { var _a; if (!organizationProp) return; if (organizationProp === true) { return { basePath: "/organization", viewPaths: _chunk2IFYYEVLcjs.organizationViewPaths, customRoles: [] }; } let logo; if (organizationProp.logo === true) { logo = { extension: "png", size: 128 }; } else if (organizationProp.logo) { logo = { upload: organizationProp.logo.upload, delete: organizationProp.logo.delete, extension: organizationProp.logo.extension || "png", size: organizationProp.logo.size || (organizationProp.logo.upload ? 256 : 128) }; } const basePath2 = ((_a = organizationProp.basePath) == null ? void 0 : _a.endsWith("/")) ? organizationProp.basePath.slice(0, -1) : organizationProp.basePath; return { ...organizationProp, logo, basePath: _nullishCoalesce(basePath2, () => ( "/organization")), customRoles: organizationProp.customRoles || [], viewPaths: { ..._chunk2IFYYEVLcjs.organizationViewPaths, ...organizationProp.viewPaths } }; }, [organizationProp]); const teams = _react.useMemo.call(void 0, () => { var _a, _b; if (!teamsProp || !organization) return; if (teamsProp === true) { return { enabled: true, customRoles: [], colors: { count: 5, prefix: "team" } }; } return { enabled: _nullishCoalesce(teamsProp.enabled, () => ( true)), customRoles: teamsProp.customRoles || [], colors: { count: _nullishCoalesce(((_a = teamsProp.colors) == null ? void 0 : _a.count), () => ( 5)), prefix: _nullishCoalesce(((_b = teamsProp.colors) == null ? void 0 : _b.prefix), () => ( "team")) } }; }, [teamsProp, organization]); const defaultMutators = _react.useMemo.call(void 0, () => { return { deleteApiKey: (params) => authClient.apiKey.delete({ ...params, fetchOptions: { throw: true } }), deletePasskey: (params) => authClient.passkey.deletePasskey({ ...params, fetchOptions: { throw: true } }), revokeDeviceSession: (params) => authClient.multiSession.revoke({ ...params, fetchOptions: { throw: true } }), revokeSession: (params) => authClient.revokeSession({ ...params, fetchOptions: { throw: true } }), setActiveSession: (params) => authClient.multiSession.setActive({ ...params, fetchOptions: { throw: true } }), updateOrganization: (params) => authClient.organization.update({ ...params, fetchOptions: { throw: true } }), updateTeam: (params) => authClient.$fetch("/organization/update-team", { method: "POST", body: params, throw: true }), updateUser: (params) => authClient.updateUser({ ...params, fetchOptions: { throw: true } }), unlinkAccount: (params) => authClient.unlinkAccount({ ...params, fetchOptions: { throw: true } }) }; }, [authClient]); const defaultHooks = _react.useMemo.call(void 0, () => { return { useSession: authClient.useSession, useListAccounts: () => useAuthData({ queryFn: authClient.listAccounts, cacheKey: "listAccounts" }), useAccountInfo: (params) => useAuthData({ queryFn: () => authClient.accountInfo(params), cacheKey: `accountInfo:${JSON.stringify(params)}` }), useListDeviceSessions: () => useAuthData({ queryFn: authClient.multiSession.listDeviceSessions, cacheKey: "listDeviceSessions" }), useListSessions: () => useAuthData({ queryFn: authClient.listSessions, cacheKey: "listSessions" }), useListPasskeys: authClient.useListPasskeys, useListApiKeys: () => useAuthData({ queryFn: authClient.apiKey.list, cacheKey: "listApiKeys" }), useActiveOrganization: authClient.useActiveOrganization, useListOrganizations: authClient.useListOrganizations, useHasPermission: (params) => useAuthData({ queryFn: () => authClient.$fetch("/organization/has-permission", { method: "POST", body: params }), cacheKey: `hasPermission:${JSON.stringify(params)}` }), useInvitation: (params) => useAuthData({ queryFn: () => authClient.organization.getInvitation(params), cacheKey: `invitation:${JSON.stringify(params)}` }), useListInvitations: (params) => useAuthData({ queryFn: () => { var _a; return authClient.$fetch( `/organization/list-invitations?organizationId=${((_a = params == null ? void 0 : params.query) == null ? void 0 : _a.organizationId) || ""}` ); }, cacheKey: `listInvitations:${JSON.stringify(params)}` }), useListUserInvitations: () => useAuthData({ queryFn: () => authClient.$fetch( "/organization/list-user-invitations" ), cacheKey: `listUserInvitations` }), useListMembers: (params) => useAuthData({ queryFn: () => { var _a; return authClient.$fetch( `/organization/list-members?organizationId=${((_a = params == null ? void 0 : params.query) == null ? void 0 : _a.organizationId) || ""}` ); }, cacheKey: `listMembers:${JSON.stringify(params)}` }), useListTeams: (params) => useAuthData({ queryFn: () => authClient.$fetch( `/organization/list-teams?organizationId=${(params == null ? void 0 : params.organizationId) || ""}` ), cacheKey: `listTeams:${JSON.stringify(params)}` }), useListTeamMembers: (params) => useAuthData({ queryFn: () => authClient.$fetch("/organization/list-team-members", { method: "POST", body: (params == null ? void 0 : params.teamId) ? { query: { teamId: params.teamId } } : void 0 }), cacheKey: `listTeamMembers:${JSON.stringify(params)}` }), useListUserTeams: () => useAuthData({ queryFn: () => authClient.$fetch("/organization/list-user-teams"), cacheKey: "listUserTeams" }) }; }, [authClient]); const viewPaths = _react.useMemo.call(void 0, () => { return { ..._chunk2IFYYEVLcjs.authViewPaths, ...viewPathsProp }; }, [viewPathsProp]); const localization = _react.useMemo.call(void 0, () => { return { ..._chunk2IFYYEVLcjs.authLocalization, ...localizationProp }; }, [localizationProp]); const hooks = _react.useMemo.call(void 0, () => { return { ...defaultHooks, ...hooksProp }; }, [defaultHooks, hooksProp]); const mutators = _react.useMemo.call(void 0, () => { return { ...defaultMutators, ...mutatorsProp }; }, [defaultMutators, mutatorsProp]); baseURL = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL; basePath = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath; const { data: sessionData } = hooks.useSession(); return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, AuthUIContext.Provider, { value: { authClient, avatar, basePath: basePath === "/" ? "" : basePath, baseURL, captcha, redirectTo, changeEmail, credentials, deleteUser, emailVerification, freshAge, genericOAuth, hooks, mutators, localization, localizeErrors, nameRequired, organization, teams, account, signUp, social, toast: toast2, navigate: navigate || defaultNavigate, replace: replace || navigate || defaultReplace, viewPaths, Link, ...props }, children: [ sessionData && organization && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, OrganizationRefetcher, {}), (captcha == null ? void 0 : captcha.provider) === "google-recaptcha-v3" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, RecaptchaV3, { children }) : children ] } ); }; // src/hooks/use-auth-data.ts function useAuthData({ queryFn, cacheKey, staleTime = 1e4 // Default 10 seconds }) { var _a; const { hooks: { useSession }, toast: toast2, localization, localizeErrors } = _react.useContext.call(void 0, AuthUIContext); const { data: sessionData, isPending: sessionPending } = useSession(); const queryFnRef = _react.useRef.call(void 0, queryFn); queryFnRef.current = queryFn; const stableCacheKey = cacheKey || queryFn.toString(); const cacheEntry = _react.useSyncExternalStore.call(void 0, _react.useCallback.call(void 0, (callback) => authDataCache.subscribe(stableCacheKey, callback), [stableCacheKey] ), _react.useCallback.call(void 0, () => authDataCache.get(stableCacheKey), [stableCacheKey] ), _react.useCallback.call(void 0, () => authDataCache.get(stableCacheKey), [stableCacheKey] ) ); const initialized = _react.useRef.call(void 0, false); const previousUserId = _react.useRef.call(void 0, void 0); const [error, setError] = _react.useState.call(void 0, null); const refetch = _react.useCallback.call(void 0, async () => { const existingRequest = authDataCache.getInFlightRequest(stableCacheKey); if (existingRequest) { try { const result = await existingRequest; if (result.error) { setError(result.error); } else { setError(null); } } catch (err) { setError(err); } return; } if ((cacheEntry == null ? void 0 : cacheEntry.data) !== void 0) { authDataCache.setRefetching(stableCacheKey, true); } const fetchPromise = queryFnRef.current(); authDataCache.setInFlightRequest(stableCacheKey, fetchPromise); try { const { data, error: error2 } = await fetchPromise; if (error2) { setError(error2); toast2({ variant: "error", message: _chunk2IFYYEVLcjs.getLocalizedError.call(void 0, { error: error2, localization, localizeErrors }) }); } else { setError(null); } authDataCache.set(stableCacheKey, data); } catch (err) { const error2 = err; setError(error2); toast2({ variant: "error", message: _chunk2IFYYEVLcjs.getLocalizedError.call(void 0, { error: error2, localization, localizeErrors }) }); } finally { authDataCache.setRefetching(stableCacheKey, false); authDataCache.removeInFlightRequest(stableCacheKey); } }, [stableCacheKey, toast2, localization, localizeErrors, cacheEntry]); _react.useEffect.call(void 0, () => { var _a2; const currentUserId = (_a2 = sessionData == null ? void 0 : sessionData.user) == null ? void 0 : _a2.id; if (!sessionData) { authDataCache.setRefetching(stableCacheKey, false); authDataCache.clear(stableCacheKey); initialized.current = false; previousUserId.current = void 0; return; } const userIdChanged = previousUserId.current !== void 0 && previousUserId.current !== currentUserId; if (userIdChanged) { authDataCache.clear(stableCacheKey); } const hasCachedData = (cacheEntry == null ? void 0 : cacheEntry.data) !== void 0; const isStale = !cacheEntry || Date.now() - cacheEntry.timestamp > staleTime; if (!initialized.current || !hasCachedData || userIdChanged || hasCachedData && isStale) { if (!hasCachedData || isStale) { initialized.current = true; refetch(); } } previousUserId.current = currentUserId; }, [ sessionData, (_a = sessionData == null ? void 0 : sessionData.user) == null ? void 0 : _a.id, stableCacheKey, refetch, cacheEntry, staleTime ]); const isPending = sessionPending || (cacheEntry == null ? void 0 : cacheEntry.data) === void 0 && !error; return { data: _nullishCoalesce((cacheEntry == null ? void 0 : cacheEntry.data), () => ( null)), isPending, isRefetching: _nullishCoalesce((cacheEntry == null ? void 0 : cacheEntry.isRefetching), () => ( false)), error, refetch }; } exports.useIsHydrated = useIsHydrated; exports.useLang = useLang; exports.useTheme = useTheme; exports.useAuthData = useAuthData; exports.useCurrentOrganization = useCurrentOrganization; exports.AuthUIContext = AuthUIContext; exports.AuthUIProvider = AuthUIProvider;